From 731a2bb2bd73d5d147db0d3eaef7d560b4ed0367 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Wed, 11 Dec 2024 16:32:12 +0100 Subject: [PATCH] Add libxcrypt MSYS2 builder This does a full client build of libxcrypt, using upstream's autotools based build chain on MSYS2, but using MSVC build tools. This requires wrappers for the MSVC build tools; ar-lib and compile are taken unmodified from automake; windres is a minimalist self-made wrapper. A prerequisite is a suitable libxcrypt patched to support MSVC builds[1]. [1] --- .github/workflows/libxcrypt_msys.yml | 79 ++++++ autotools/ar-lib | 279 +++++++++++++++++++++ autotools/compile | 351 +++++++++++++++++++++++++++ autotools/windres | 49 ++++ patches/libxcrypt.patch | 54 +++++ 5 files changed, 812 insertions(+) create mode 100644 .github/workflows/libxcrypt_msys.yml create mode 100644 autotools/ar-lib create mode 100644 autotools/compile create mode 100644 autotools/windres create mode 100644 patches/libxcrypt.patch diff --git a/.github/workflows/libxcrypt_msys.yml b/.github/workflows/libxcrypt_msys.yml new file mode 100644 index 0000000..5d79678 --- /dev/null +++ b/.github/workflows/libxcrypt_msys.yml @@ -0,0 +1,79 @@ +name: Build libxcrypt_msys +on: + workflow_dispatch: + inputs: + version: + description: libxcrypt tag to build + required: true + php: + description: PHP version to build for + required: true +defaults: + run: + shell: cmd +jobs: + build: + strategy: + matrix: + arch: [x64, x86] + runs-on: windows-2022 + steps: + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout winlib-builder + uses: actions/checkout@v4 + with: + path: winlib-builder + - name: Checkout libxcrypt + uses: actions/checkout@v4 + with: + path: libxcrypt + repository: cmb69/libxcrypt + ref: ${{github.event.inputs.version}} + - name: Compute virtual inputs + id: virtuals + run: powershell winlib-builder/scripts/compute-virtuals -version ${{github.event.inputs.php}} -arch ${{matrix.arch}} + - name: Patch libxcrypt + run: cd libxcrypt && git apply --ignore-whitespace ..\winlib-builder\patches\libxcrypt.patch + - name: Setup MSVC development environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{matrix.arch}} + toolset: ${{steps.virtuals.outputs.toolset}} + - name: Create build script + run: | + cd libxcrypt + set WORKSPACE=${{github.workspace}} + set WORKSPACE=%WORKSPACE:\=/% + set WORKSPACE=/%WORKSPACE::=% + > winlibs.sh echo export PATH=/usr/local/bin:/usr/bin:/bin:/opt/bin:$PATH + >>winlibs.sh echo pacman -S --noconfirm autotools + >>winlibs.sh echo autoreconf -f -i + >>winlibs.sh echo ./configure --host=x86_64-w64-mingw32 --prefix %WORKSPACE%/install \ + >>winlibs.sh echo CC='%WORKSPACE%/winlib-builder/autotools/compile cl -nologo -std:c11 -Zc:__STDC__' \ + >>winlibs.sh echo AR='%WORKSPACE%/winlib-builder/autotools/ar-lib lib' \ + >>winlibs.sh echo NM='dumpbin -symbols' \ + >>winlibs.sh echo STRIP=':' \ + >>winlibs.sh echo RANLIB=':' \ + >>winlibs.sh echo WINDRES='%WORKSPACE%/winlib-builder/autotools/windres' \ + >>winlibs.sh echo RC='%WORKSPACE%/winlib-builder/autotools/windres' \ + >>winlibs.sh echo CFLAGS='-MD -Ox -Z7 -D_CRT_DECLARE_NONSTDC_NAMES=1' \ + >>winlibs.sh echo LDFLAGS='-debug' \ + >>winlibs.sh echo LIBS='-lBcrypt' + >>winlibs.sh echo make + >>winlibs.sh echo make check + >>winlibs.sh echo make install + - name: Build libxcrypt + run: | + cd libxcrypt + set CHERE_INVOKING=yes + set MSYS2_PATH_TYPE=inherit + set MSYS2_ARG_CONV_EXCL=* + C:\msys64\usr\bin\bash.exe -lc ./winlibs.sh + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{github.event.inputs.version}}-${{steps.virtuals.outputs.vs}}-${{matrix.arch}} + path: install diff --git a/autotools/ar-lib b/autotools/ar-lib new file mode 100644 index 0000000..1521987 --- /dev/null +++ b/autotools/ar-lib @@ -0,0 +1,279 @@ +#! /bin/sh +# Wrapper for Microsoft lib.exe + +me=ar-lib +scriptversion=2024-06-19.01; # UTC + +# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Written by Peter Rosin . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + + +# func_error message +func_error () +{ + echo "$me: $1" 1>&2 + exit 1 +} + +file_conv= + +# func_file_conv build_file +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN* | MSYS*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv in + mingw) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin | msys) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_at_file at_file operation archive +# Iterate over all members in AT_FILE performing OPERATION on ARCHIVE +# for each of them. +# When interpreting the content of the @FILE, do NOT use func_file_conv, +# since the user would need to supply preconverted file names to +# binutils ar, at least for MinGW. +func_at_file () +{ + operation=$2 + archive=$3 + at_file_contents=`cat "$1"` + eval set x "$at_file_contents" + shift + + for member + do + $AR -NOLOGO $operation:"$member" "$archive" || exit $? + done +} + +case $1 in + '') + func_error "no command. Try '$0 --help' for more information." + ;; + -h | --h*) + cat <. +GNU Automake home page: . +General help using GNU software: . +EOF + exit $? + ;; + -v | --v*) + echo "$me (GNU Automake) $scriptversion" + exit $? + ;; +esac + +if test $# -lt 3; then + func_error "you must specify a program, an action and an archive" +fi + +AR=$1 +shift +while : +do + if test $# -lt 2; then + func_error "you must specify a program, an action and an archive" + fi + case $1 in + -lib | -LIB \ + | -ltcg | -LTCG \ + | -machine* | -MACHINE* \ + | -subsystem* | -SUBSYSTEM* \ + | -verbose | -VERBOSE \ + | -wx* | -WX* ) + AR="$AR $1" + shift + ;; + -nologo | -NOLOGO) + # We always invoke AR with -nologo, so don't need to add it again. + shift + ;; + *) + action=$1 + shift + break + ;; + esac +done +orig_archive=$1 +shift +func_file_conv "$orig_archive" +archive=$file + +# strip leading dash in $action +action=${action#-} + +delete= +extract= +list= +quick= +replace= +index= +create= + +while test -n "$action" +do + case $action in + d*) delete=yes ;; + x*) extract=yes ;; + t*) list=yes ;; + q*) quick=yes ;; + r*) replace=yes ;; + s*) index=yes ;; + S*) ;; # the index is always updated implicitly + c*) create=yes ;; + u*) ;; # TODO: don't ignore the update modifier + v*) ;; # TODO: don't ignore the verbose modifier + *) + func_error "unknown action specified" + ;; + esac + action=${action#?} +done + +case $delete$extract$list$quick$replace,$index in + yes,* | ,yes) + ;; + yesyes*) + func_error "more than one action specified" + ;; + *) + func_error "no action specified" + ;; +esac + +if test -n "$delete"; then + if test ! -f "$orig_archive"; then + func_error "archive not found" + fi + for member + do + case $1 in + @*) + func_at_file "${1#@}" -REMOVE "$archive" + ;; + *) + func_file_conv "$1" + $AR -NOLOGO -REMOVE:"$file" "$archive" || exit $? + ;; + esac + done + +elif test -n "$extract"; then + if test ! -f "$orig_archive"; then + func_error "archive not found" + fi + if test $# -gt 0; then + for member + do + case $1 in + @*) + func_at_file "${1#@}" -EXTRACT "$archive" + ;; + *) + func_file_conv "$1" + $AR -NOLOGO -EXTRACT:"$file" "$archive" || exit $? + ;; + esac + done + else + $AR -NOLOGO -LIST "$archive" | tr -d '\r' | sed -e 's/\\/\\\\/g' \ + | while read member + do + $AR -NOLOGO -EXTRACT:"$member" "$archive" || exit $? + done + fi + +elif test -n "$quick$replace"; then + if test ! -f "$orig_archive"; then + if test -z "$create"; then + echo "$me: creating $orig_archive" + fi + orig_archive= + else + orig_archive=$archive + fi + + for member + do + case $1 in + @*) + func_file_conv "${1#@}" + set x "$@" "@$file" + ;; + *) + func_file_conv "$1" + set x "$@" "$file" + ;; + esac + shift + shift + done + + if test -n "$orig_archive"; then + $AR -NOLOGO -OUT:"$archive" "$orig_archive" "$@" || exit $? + else + $AR -NOLOGO -OUT:"$archive" "$@" || exit $? + fi + +elif test -n "$list"; then + if test ! -f "$orig_archive"; then + func_error "archive not found" + fi + $AR -NOLOGO -LIST "$archive" || exit $? +fi diff --git a/autotools/compile b/autotools/compile new file mode 100644 index 0000000..49b3d05 --- /dev/null +++ b/autotools/compile @@ -0,0 +1,351 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2024-06-19.01; # UTC + +# Copyright (C) 1999-2024 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN* | MSYS*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/* | msys/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.lo | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +GNU Automake home page: . +General help using GNU software: . +EOF + exit $? + ;; + -v | --v*) + echo "compile (GNU Automake) $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ + clang-cl | *[/\\]clang-cl | clang-cl.exe | *[/\\]clang-cl.exe | \ + icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/autotools/windres b/autotools/windres new file mode 100644 index 0000000..78bf5a5 --- /dev/null +++ b/autotools/windres @@ -0,0 +1,49 @@ +#! /bin/sh + +DEFINES=() + +while [[ $# -gt 0 ]]; do + case $1 in + -i) + INPUT="`cygpath -w $2`" + shift + shift + ;; + --output-format=*) + if test "${1#*=}" != coff; then + echo "Unsupported output format ${1#*=}" + exit 1 + fi + shift + ;; + -O) + if test "$2" != coff; then + echo "Unsupported output format $2" + exit 1 + fi + shift + shift + ;; + -o) + OUTPUT="`cygpath -w $2`" + shift + shift + ;; + -D*) + DEFINES+=("$1") + shift + ;; + -*) + echo "Unsupported option $1" + exit 1 + ;; + *) + INPUT="`cygpath -w $1`" + shift + ;; + esac +done + +rc.exe ${DEFINES[@]} -fo ${OUTPUT}.res ${INPUT} +cvtres.exe -machine:${Platform} -out:${OUTPUT} ${OUTPUT}.res +rm ${OUTPUT}.res diff --git a/patches/libxcrypt.patch b/patches/libxcrypt.patch new file mode 100644 index 0000000..99d4db8 --- /dev/null +++ b/patches/libxcrypt.patch @@ -0,0 +1,54 @@ + Makefile.am | 4 ---- + configure.ac | 1 + + test/alg-yescrypt.c | 2 +- + 3 files changed, 2 insertions(+), 5 deletions(-) + +diff --git a/Makefile.am b/Makefile.am +index 5c07059..08ed7ee 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -368,19 +368,15 @@ check_PROGRAMS = \ + test/alg-sha256 \ + test/alg-sha512 \ + test/alg-yescrypt \ +- test/badsalt \ + test/badsetting \ + test/byteorder \ + test/checksalt \ +- test/compile-strong-alias \ +- test/crypt-badargs \ + test/crypt-gost-yescrypt \ + test/explicit-bzero \ + test/gensalt \ + test/gensalt-extradata \ + test/gensalt-nthash \ + test/getrandom-fallbacks \ +- test/getrandom-interface \ + test/preferred-method \ + test/short-outbuf \ + test/special-char-salt +diff --git a/configure.ac b/configure.ac +index 4b8afd8..1523e92 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -28,6 +28,7 @@ m4_pattern_allow([PKG_INSTALLDIR]) + # Checks for programs. + AC_CANONICAL_HOST + AC_PROG_CC ++ac_prog_cc_stdc=c11 + + # Dependencies + PKG_PROG_PKG_CONFIG +diff --git a/test/alg-yescrypt.c b/test/alg-yescrypt.c +index 8112dac..3303368 100644 +--- a/test/alg-yescrypt.c ++++ b/test/alg-yescrypt.c +@@ -158,7 +158,7 @@ int main(void) + { + int i; + +- setvbuf(stdout, NULL, _IOLBF, 0); ++ // setvbuf(stdout, NULL, _IOLBF, 0); + + #ifdef TEST_PBKDF2_SHA256 + print_PBKDF2_SHA256("password", "salt", 1, 20);