diff --git a/.admin/run_admin.sh b/.admin/run_admin.sh index e31dd54e51..d42aac57de 100644 --- a/.admin/run_admin.sh +++ b/.admin/run_admin.sh @@ -4,21 +4,24 @@ ARCH=x86_64-w64-mingw32 WORKDIR="$(cd "$(dirname "$0")" && pwd)" -BINUTILS_VERSION=2.41 -BUSYBOX_VERSION=FRP-5236-g7dff7f376 -CPPCHECK_VERSION=2.10 +BINUTILS_VERSION=2.42 +BUSYBOX_VERSION=FRP-5467-g9376eebd8 CTAGS_VERSION=6.0.0 -EXPAT_VERSION=2.5.0 -GCC_VERSION=13.2.0 -GDB_VERSION=13.1 +CPPCHECK_VERSION=2.10 +EXPAT_VERSION=2.6.2 +GCC_VERSION=14.2.0 +GDB_VERSION=15.1 GMP_VERSION=6.3.0 LIBICONV_VERSION=1.17 MAKE_VERSION=4.4.1 -MINGW_VERSION=11.0.1 +MINGW_VERSION=12.0.0 MPC_VERSION=1.3.1 MPFR_VERSION=4.2.1 -NASM_VERSION=2.15.05 +NASM_VERSION=2.15.05rc2 PDCURSES_VERSION=3.9 VIM_VERSION=9.0 +Z7_VERSION=2301 + + +VERSION=1.1.0 -VERSION=1.0.0-hf1 diff --git a/.gitignore b/.gitignore index 3c59d5cc23..d6804d3070 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .gnu-windows/make .gnu-windows/ad-installer *.exe -*.dll \ No newline at end of file +*.dll +.gnu-windows/gnu-windows.rc diff --git a/.gitmodules b/.gitmodules index 2286b18a34..5e1743251e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,45 +1,51 @@ -[submodule "PDCurses"] - path = PDCurses - url = https://github.com/wmcbrine/PDCurses.git -[submodule "binutils-gdb"] - path = binutils-gdb - url = https://sourceware.org/git/binutils-gdb.git +[submodule "binutils"] + path = binutils + url = https://github.com/tfslabs/binutils.git [submodule "busybox-w32"] path = busybox-w32 - url = git://git.frippery.org/busybox-w32 -[submodule "cppcheck"] - path = cppcheck - url = https://github.com/danmar/cppcheck.git + url = https://github.com/tfslabs/busybox-w32.git [submodule "ctags"] path = ctags - url = https://github.com/universal-ctags/ctags.git -[submodule "libexpat"] - path = libexpat - url = https://github.com/libexpat/libexpat.git + url = https://github.com/tfslabs/ctags.git +[submodule "expat"] + path = expat + url = https://github.com/tfslabs/expat.git [submodule "gcc"] path = gcc - url = https://gcc.gnu.org/git/gcc.git + url = https://github.com/tfslabs/gcc.git +[submodule "gdb"] + path = gdb + url = https://github.com/tfslabs/gdb.git [submodule "gmp"] path = gmp - url = https://github.com/gmp-mirror/gmp.git + url = https://github.com/tfslabs/gmp.git [submodule "libiconv"] path = libiconv - url = https://git.savannah.gnu.org/git/libiconv.git + url = https://github.com/tfslabs/libiconv.git [submodule "make"] path = make - url = https://git.savannah.gnu.org/git/make.git + url = https://github.com/tfslabs/make.git [submodule "mingw-w64"] path = mingw-w64 - url = https://github.com/mingw-w64/mingw-w64.git -[submodule "libmpc"] - path = libmpc - url = https://github.com/nicolapiccinelli/libmpc.git + url = https://github.com/tfslabs/mingw-w64.git +[submodule "mpc"] + path = mpc + url = https://github.com/tfslabs/mpc.git [submodule "mpfr"] path = mpfr - url = https://gitlab.inria.fr/mpfr/mpfr.git -[submodule "nasm"] - path = nasm - url = https://github.com/netwide-assembler/nasm.git + url = https://github.com/tfslabs/mpfr.git +[submodule "pdcurses"] + path = pdcurses + url = https://github.com/tfslabs/pdcurses.git [submodule "vim"] path = vim - url = https://github.com/vim/vim.git \ No newline at end of file + url = https://github.com/tfslabs/vim.git +[submodule "7z"] + path = 7z + url = https://github.com/tfslabs/7z.git +[submodule "nasm"] + path = nasm + url = https://github.com/tfslabs/nasm.git +[submodule "cppcheck"] + path = cppcheck + url = https://github.com/tfslabs/cppcheck.git diff --git a/.gnu-windows/build.sh b/.gnu-windows/build.sh index 925026dbda..96a4224464 100644 --- a/.gnu-windows/build.sh +++ b/.gnu-windows/build.sh @@ -9,64 +9,49 @@ SOURCE_CODE="$WORKDIR/src" GNU_FOLDER="$WORKDIR/.." PATH="$BOOTSTRAP/bin:${PATH}" -BINUTILS_VERSION=2.41 -BUSYBOX_VERSION=FRP-5236-g7dff7f376 -CPPCHECK_VERSION=2.10 -CTAGS_VERSION=6.0.0 -EXPAT_VERSION=2.5.0 -GCC_VERSION=13.2.0 -GDB_VERSION=13.1 -GMP_VERSION=6.3.0 -LIBICONV_VERSION=1.17 -MAKE_VERSION=4.4.1 -MINGW_VERSION=11.0.1 -MPC_VERSION=1.3.1 -MPFR_VERSION=4.2.1 -NASM_VERSION=2.15.05 -PDCURSES_VERSION=3.9 -VERSION=1.21.0 -VIM_VERSION=9.0 +VERSION=1.1.0 cd $WORKDIR -# Cross-build system - -#cd $GNU_FOLDER/binutils-$BINUTILS_VERSION -#sed -ri 's/(static bool insert_timestamp = )/\1!/' ld/emultempl/pe*.em +# Build cross-compiler +cd $GNU_FOLDER/binutils +sed -ri 's/(static bool insert_timestamp = )/\1!/' ld/emultempl/pe*.em \ + && sed -ri 's/(int pe_enable_stdcall_fixup = )/\1!!/' ld/emultempl/pe*.em \ + && cat $SOURCE_CODE/binutils-*.patch | patch -p1 mkdir $MAKE_FOLDER/x-binutils && cd "$_" -chmod +x $GNU_FOLDER/binutils-$BINUTILS_VERSION/configure -$GNU_FOLDER/binutils-$BINUTILS_VERSION/configure \ +chmod +x $GNU_FOLDER/binutils/configure +$GNU_FOLDER/binutils/configure \ --prefix=$BOOTSTRAP \ --with-sysroot=$BOOTSTRAP/$ARCH \ --target=$ARCH \ --disable-nls \ --with-static-standard-libraries \ --disable-multilib \ - && make MAKEINFO=true \ + && make MAKEINFO=true -j$(nproc) \ && make MAKEINFO=true install -cd $WORKDIR -#sed -i /OpenThreadToken/d $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/lib32/kernel32.def +# Fixes i686 Windows XP regression +# https://sourceforge.net/p/mingw-w64/bugs/821/ +sed -i /OpenThreadToken/d $GNU_FOLDER/mingw-w64/mingw-w64-crt/lib32/kernel32.def + mkdir $MAKE_FOLDER/x-mingw-headers && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-headers/configure +$GNU_FOLDER/mingw-w64/mingw-w64-headers/configure \ --prefix=$BOOTSTRAP/$ARCH \ --host=$ARCH \ --with-default-msvcrt=msvcrt-os \ - && make \ + && make -j$(nproc) \ && make install -cd $BOOTSTRAP -ln -s $ARCH mingw +cd $BOOTSTRAP && ln -s $ARCH mingw mkdir $MAKE_FOLDER/x-gcc && cd "$_" -mkdir $BOOTSTRAP/src && cp $SOURCE_CODE/gcc-*.patch $BOOTSTRAP/src/ -#cat $BOOTSTRAP/src/gcc-*.patch | patch -d $GNU_FOLDER/gcc-$GCC_VERSION -p1 -chmod +x $GNU_FOLDER/gcc-$GCC_VERSION/configure -$GNU_FOLDER/gcc-$GCC_VERSION/configure \ +cat $SOURCE_CODE/gcc-*.patch | patch -d $GNU_FOLDER/gcc -p1 +chmod +x $GNU_FOLDER/gcc/configure +$GNU_FOLDER/gcc/configure \ --prefix=$BOOTSTRAP \ --with-sysroot=$BOOTSTRAP \ --target=$ARCH \ @@ -74,7 +59,7 @@ $GNU_FOLDER/gcc-$GCC_VERSION/configure \ --disable-shared \ --with-pic \ --with-gnu-ld \ - --enable-languages=c,c++ \ + --enable-languages=c,c++,fortran \ --enable-libgomp \ --enable-threads=posix \ --enable-version-specific-runtime-libs \ @@ -88,17 +73,18 @@ $GNU_FOLDER/gcc-$GCC_VERSION/configure \ CFLAGS="-Os" \ CXXFLAGS="-Os" \ LDFLAGS="-s" \ - && make all-gcc \ + && make -j$(nproc) all-gcc \ && make install-gcc mkdir -p $BOOTSTRAP/$ARCH/lib \ - && chmod +x $BOOTSTRAP/bin/$ARCH-gcc \ - && CC=$BOOTSTRAP/bin/$ARCH-gcc DESTDIR=$BOOTSTRAP/$ARCH/lib/ sh $SOURCE_CODE/libmemory.c \ - && CC=$BOOTSTRAP/bin/$ARCH-gcc DESTDIR=$BOOTSTRAP/$ARCH/lib/ sh $SOURCE_CODE/libchkstk.S + && CC=$ARCH-gcc DESTDIR=$BOOTSTRAP/$ARCH/lib/ sh $SOURCE_CODE/libmemory.c \ + && ln $BOOTSTRAP/$ARCH/lib/libmemory.a $BOOTSTRAP/$ARCH/lib/ \ + && CC=$ARCH-gcc DESTDIR=$BOOTSTRAP/$ARCH/lib/ sh $SOURCE_CODE/libchkstk.S \ + && ln $BOOTSTRAP/$ARCH/lib/libchkstk.a $BOOTSTRAP/$ARCH/lib/ mkdir $MAKE_FOLDER/x-mingw-crt && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-crt/configure +$GNU_FOLDER/mingw-w64/mingw-w64-crt/configure \ --prefix=$BOOTSTRAP/$ARCH \ --with-sysroot=$BOOTSTRAP/$ARCH \ --host=$ARCH \ @@ -108,12 +94,12 @@ $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ --enable-lib64 \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/x-winpthreads && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-libraries/winpthreads/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-libraries/winpthreads/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-libraries/winpthreads/configure +$GNU_FOLDER/mingw-w64/mingw-w64-libraries/winpthreads/configure \ --prefix=$BOOTSTRAP/$ARCH \ --with-sysroot=$BOOTSTRAP/$ARCH \ --host=$ARCH \ @@ -121,17 +107,17 @@ $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-libraries/winpthreads/configure --disable-shared \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install -cd $MAKE_FOLDER/x-gcc -make -make install +cd $MAKE_FOLDER/x-gcc \ + && make -j$(nproc) \ + && make install # Cross-compile GCC mkdir $MAKE_FOLDER/binutils && cd "$_" -$GNU_FOLDER/binutils-$BINUTILS_VERSION/configure \ +$GNU_FOLDER/binutils/configure \ --prefix=$BOOTSTRAP \ --with-sysroot=$BOOTSTRAP/$ARCH \ --host=$ARCH \ @@ -140,12 +126,13 @@ $GNU_FOLDER/binutils-$BINUTILS_VERSION/configure \ --with-static-standard-libraries \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make MAKEINFO=true \ - && make MAKEINFO=true install + && make MAKEINFO=true -j$(nproc) \ + && make MAKEINFO=true install \ + && rm $BOOTSTRAP/bin/elfedit.exe $BOOTSTRAP/bin/readelf.exe mkdir $MAKE_FOLDER/gmp && cd "$_" -chmod +x $GNU_FOLDER/gmp-$GMP_VERSION/configure -$GNU_FOLDER/gmp-$GMP_VERSION/configure \ +chmod +x $GNU_FOLDER/gmp/configure +$GNU_FOLDER/gmp/configure \ --prefix=$BOOTSTRAP \ --host=$ARCH \ --enable-static \ @@ -153,12 +140,12 @@ $GNU_FOLDER/gmp-$GMP_VERSION/configure \ CFLAGS="-Os" \ CXXFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/mpfr && cd "$_" -chmod +x $GNU_FOLDER/mpfr-$MPFR_VERSION/configure -$GNU_FOLDER/mpfr-$MPFR_VERSION/configure \ +chmod +x $GNU_FOLDER/mpfr/configure +$GNU_FOLDER/mpfr/configure \ --prefix=$BOOTSTRAP \ --host=$ARCH \ --with-gmp-include=$BOOTSTRAP/include \ @@ -167,12 +154,12 @@ $GNU_FOLDER/mpfr-$MPFR_VERSION/configure \ --disable-shared \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/mpc && cd "$_" -chmod +x $GNU_FOLDER/mpc-$MPC_VERSION/configure -$GNU_FOLDER/mpc-$MPC_VERSION/configure \ +chmod +x $GNU_FOLDER/mpc/configure +$GNU_FOLDER/mpc/configure \ --prefix=$BOOTSTRAP \ --host=$ARCH \ --with-gmp-include=$BOOTSTRAP/include \ @@ -183,21 +170,21 @@ $GNU_FOLDER/mpc-$MPC_VERSION/configure \ --disable-shared \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/mingw-headers && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-headers/configure +$GNU_FOLDER/mingw-w64/mingw-w64-headers/configure \ --prefix=$BOOTSTRAP/$ARCH \ --host=$ARCH \ --with-default-msvcrt=msvcrt-os \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/mingw-crt && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-crt/configure +$GNU_FOLDER/mingw-w64/mingw-w64-crt/configure \ --prefix=$BOOTSTRAP/$ARCH \ --with-sysroot=$BOOTSTRAP/$ARCH \ --host=$ARCH \ @@ -207,24 +194,24 @@ $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ --enable-lib64 \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/winpthreads && cd "$_" -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-libraries/winpthreads/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-libraries/winpthreads/configure \ +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-libraries/winpthreads/configure +$GNU_FOLDER/mingw-w64/mingw-w64-libraries/winpthreads/configure \ --prefix=$BOOTSTRAP/$ARCH \ --with-sysroot=$BOOTSTRAP/$ARCH \ --host=$ARCH \ --enable-static \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/gcc && cd "$_" -chmod +x $GNU_FOLDER/gcc-$GCC_VERSION/configure -$GNU_FOLDER/gcc-$GCC_VERSION/configure \ +chmod +x $GNU_FOLDER/gcc/configure +$GNU_FOLDER/gcc/configure \ --prefix=$BOOTSTRAP \ --with-sysroot=$BOOTSTRAP/$ARCH \ --with-native-system-header-dir=/include \ @@ -240,7 +227,7 @@ $GNU_FOLDER/gcc-$GCC_VERSION/configure \ --with-mpc-lib=$BOOTSTRAP/lib \ --with-mpfr-include=$BOOTSTRAP/include \ --with-mpfr-lib=$BOOTSTRAP/lib \ - --enable-languages=c,c++ \ + --enable-languages=c,c++,fortran \ --enable-libgomp \ --enable-lto \ --enable-threads=posix \ @@ -257,13 +244,13 @@ $GNU_FOLDER/gcc-$GCC_VERSION/configure \ CFLAGS="-Os" \ CXXFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install \ && $BOOTSTRAP/bin/$ARCH-gcc -DEXE=g++.exe -DCMD=c++ \ -Os -fno-asynchronous-unwind-tables \ -Wl,--gc-sections -s -nostdlib \ -o $BOOTSTRAP/bin/c++.exe \ - $WORKDIR/src/alias.c -lkernel32 + $SOURCE_CODE/alias.c -lkernel32 $BOOTSTRAP/bin/$ARCH-gcc -DEXE=gcc.exe -DCMD=cc \ -Os -fno-asynchronous-unwind-tables -Wl,--gc-sections -s -nostdlib \ @@ -283,67 +270,73 @@ $BOOTSTRAP/bin/$ARCH-gcc -DEXE=gcc.exe -DCMD=cc \ -Wl,--gc-sections -s -nostdlib \ -o $BOOTSTRAP/bin/$ARCH-{}.exe $SOURCE_CODE/alias.c -lkernel32 -mkdir $SOURCE_CODE/mingw-tools-gendef && cd "$_" -cp $SOURCE_CODE/gendef-silent.patch $BOOTSTRAP/src/ -#patch -d $GNU_FOLDER/mingw-w64-v$MINGW_VERSION -p1 <$BOOTSTRAP/src/gendef-silent.patch \ -chmod +x $GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-tools/gendef/configure -$GNU_FOLDER/mingw-w64-v$MINGW_VERSION/mingw-w64-tools/gendef/configure \ +# Build some extra development tools, alongside with the primary development tools + +mkdir $MAKE_FOLDER/mingw-tools-gendef && cd "$_" +patch -d $GNU_FOLDER/mingw-w64 -p1 < $SOURCE_CODE/gendef-silent.patch +chmod +x $GNU_FOLDER/mingw-w64/mingw-w64-tools/gendef/configure +$GNU_FOLDER/mingw-w64/mingw-w64-tools/gendef/configure \ --host=$ARCH \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && cp gendef.exe $BOOTSTRAP/bin/ -mkdir $MAKE_FOLDER/expat && cd "$_" -chmod +x $GNU_FOLDER/expat-$EXPAT_VERSION/configure -$GNU_FOLDER/expat-$EXPAT_VERSION/configure \ +cd $GNU_FOLDER/expat +chmod +x $GNU_FOLDER/expat/configure +$GNU_FOLDER/expat/configure \ --prefix=$BOOTSTRAP \ --host=$ARCH \ + CPPFLAGS=-DXML_UNICODE \ + --without-xmlwf \ + --disable-shared \ + --without-docbook \ + --without-examples \ + --without-tests \ + --disable-xml-context \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install -cd $GNU_FOLDER/PDCurses-$PDCURSES_VERSION -make -C wincon \ - CC=$BOOTSTRAP/bin/$ARCH-gcc AR=$ARCH-ar CFLAGS="-I.. -Os -DPDC_WIDE" pdcurses.a \ +cd $GNU_FOLDER/pdcurses +make -j$(nproc) -C wincon CC=$BOOTSTRAP/bin/$ARCH-gcc AR=$ARCH-ar CFLAGS="-I.. -Os -DPDC_WIDE" pdcurses.a \ && cp wincon/pdcurses.a $BOOTSTRAP/lib/libcurses.a \ && cp curses.h $BOOTSTRAP/include mkdir $MAKE_FOLDER/libiconv && cd "$_" -chmod +x $GNU_FOLDER/libiconv-$LIBICONV_VERSION/configure -$GNU_FOLDER/libiconv-$LIBICONV_VERSION/configure \ +chmod +x $GNU_FOLDER/libiconv/configure +$GNU_FOLDER/libiconv/configure \ --prefix=$BOOTSTRAP \ --host=$ARCH \ --disable-nls \ --disable-shared \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && make install mkdir $MAKE_FOLDER/gdb && cd "$_" -cp $SOURCE_CODE/gdb-*.patch $BOOTSTRAP/src/ -#cat $SOURCE_CODE/gdb-*.patch | patch -d $GNU_FOLDER/gdb-$GDB_VERSION -p1 \ -# && sed -i 's/quiet = 0/quiet = 1/' $GNU_FOLDER/gdb-$GDB_VERSION/gdb/main.c \ -chmod +x $GNU_FOLDER/gdb-$GDB_VERSION/configure -$GNU_FOLDER/gdb-$GDB_VERSION/configure \ +cat $SOURCE_CODE/gdb-*.patch | patch -d $GNU_FOLDER/gdb -p1 \ + && sed -i 's/quiet = 0/quiet = 1/' $GNU_FOLDER/gdb/gdb/main.c +chmod +x $GNU_FOLDER/gdb/configure +$GNU_FOLDER/gdb/configure \ --host=$ARCH \ --enable-tui \ CFLAGS="-Os -DPDC_WIDE -I$BOOTSTRAP/include" \ CXXFLAGS="-Os -DPDC_WIDE -I$BOOTSTRAP/include" \ LDFLAGS="-s -L$BOOTSTRAP/lib" \ - && make MAKEINFO=true \ + && make MAKEINFO=true -j$(nproc) \ && cp gdb/.libs/gdb.exe gdbserver/gdbserver.exe $BOOTSTRAP/bin/ mkdir $MAKE_FOLDER/make && cd "$_" -chmod +x $GNU_FOLDER/make-$MAKE_VERSION/configure -$GNU_FOLDER/make-$MAKE_VERSION/configure \ +chmod +x $GNU_FOLDER/make/configure +$GNU_FOLDER/make/configure \ --host=$ARCH \ --disable-nls \ CFLAGS="-Os" \ LDFLAGS="-s" \ - && make \ + && make -j$(nproc) \ && cp make.exe $BOOTSTRAP/bin/ \ && $BOOTSTRAP/bin/$ARCH-gcc -DEXE=make.exe -DCMD=make \ -Os -fno-asynchronous-unwind-tables \ @@ -351,9 +344,8 @@ $GNU_FOLDER/make-$MAKE_VERSION/configure \ -o $BOOTSTRAP/bin/mingw32-make.exe $SOURCE_CODE/alias.c -lkernel32 cd $GNU_FOLDER/busybox-w32 -#cp $SOURCE_CODE/busybox-* $BOOTSTRAP/src/ cat $SOURCE_CODE/busybox-*.patch | patch -p1 -make mingw64_defconfig \ +make mingw64u_defconfig \ && sed -ri 's/^(CONFIG_AR)=y/\1=n/' .config \ && sed -ri 's/^(CONFIG_ASCII)=y/\1=n/' .config \ && sed -ri 's/^(CONFIG_DPKG\w*)=y/\1=n/' .config \ @@ -369,12 +361,11 @@ make mingw64_defconfig \ && sed -ri 's/^(CONFIG_UNLINK)=y/\1=n/' .config \ && sed -ri 's/^(CONFIG_VI)=y/\1=n/' .config \ && sed -ri 's/^(CONFIG_XXD)=y/\1=n/' .config \ - && make CROSS_COMPILE=$ARCH- \ + && make -j$(nproc) CROSS_COMPILE=$ARCH- \ CONFIG_EXTRA_CFLAGS="-D_WIN32_WINNT=0x502" \ && cp busybox.exe $BOOTSTRAP/bin/ cd $BOOTSTRAP/bin - $BOOTSTRAP/bin/$ARCH-gcc -Os -fno-asynchronous-unwind-tables -Wl,--gc-sections -s \ -nostdlib -o alias.exe $SOURCE_CODE/busybox-alias.c -lkernel32 \ && printf '%s\n' arch ash awk base32 base64 basename bash bc bunzip2 bzcat \ @@ -392,7 +383,7 @@ $BOOTSTRAP/bin/$ARCH-gcc -Os -fno-asynchronous-unwind-tables -Wl,--gc-sections - wc wget which whoami whois xargs xz xzcat yes zcat \ | xargs -I{} cp alias.exe $BOOTSTRAP/bin/{}.exe -cd $GNU_FOLDER/vim90/src +cd $GNU_FOLDER/vim/src ARCH= make -f Make_ming.mak \ OPTIMIZE=SIZE STATIC_STDCPLUS=yes HAS_GCC_EH=no \ UNDER_CYGWIN=yes CROSS=yes CROSS_COMPILE=$ARCH- \ @@ -412,44 +403,39 @@ ARCH= make -f Make_ming.mak \ '$VIMRUNTIME/tutor/tutor' '%TMP%/tutor%RANDOM%' \ >$BOOTSTRAP/bin/vimtutor.bat -# NOTE: nasm's configure script is broken, so no out-of-source build -chmod +x $GNU_FOLDER/nasm-$NASM_VERSION/configure && chmod +x $GNU_FOLDER/nasm-$NASM_VERSION/autogen.sh -cd $GNU_FOLDER/nasm-$NASM_VERSION/ -$GNU_FOLDER/nasm-$NASM_VERSION/autogen.sh -mkdir $MAKE_FOLDER/nasm-$NASM_VERSION && cd "$_" -$GNU_FOLDER/nasm-$NASM_VERSION/configure \ - --host=$ARCH \ - CFLAGS="-Os" \ - LDFLAGS="-s" \ - && mkdir include \ - && make \ +cd $GNU_FOLDER/nasm/ +chmod +x $GNU_FOLDER/nasm/autogen.sh +$GNU_FOLDER/nasm/autogen.sh +$GNU_FOLDER/nasm/configure \ + --prefix=$BOOTSTRAP \ + --host=$ARCH \ + CFLAGS="-Os" \ + LDFLAGS="-s" \ + && make -j$(nproc) \ && cp nasm.exe ndisasm.exe $BOOTSTRAP/bin -cd $GNU_FOLDER/ctags-$CTAGS_VERSION -sed -i RT_MANIFEST/d win32/ctags.rc \ - && make -f mk_mingw.mak CC=gcc packcc.exe \ - && make -f mk_mingw.mak \ +cd $GNU_FOLDER/ctags +sed -i /RT_MANIFEST/d win32/ctags.rc \ + && make -j$(nproc) -f mk_mingw.mak CC=gcc packcc.exe \ + && make -j$(nproc) -f mk_mingw.mak \ CC=$BOOTSTRAP/bin/$ARCH-gcc WINDRES=$ARCH-windres \ OPT= CFLAGS=-Os LDFLAGS=-s \ && cp ctags.exe $BOOTSTRAP/bin/ -cd $GNU_FOLDER/cppcheck-$CPPCHECK_VERSION -cp $SOURCE_CODE/cppcheck* $BOOTSTRAP/src/ -cat $BOOTSTRAP/src/cppcheck-*.patch | patch -p1 \ - && make -f $BOOTSTRAP/src/cppcheck.mak CXX=$ARCH-g++ \ +cd $GNU_FOLDER/cppcheck +cat $SOURCE_CODE/cppcheck-*.patch | patch -p1 \ + && make -f $SOURCE_CODE/cppcheck.mak CXX=$ARCH-g++ \ && mkdir $BOOTSTRAP/share/cppcheck/ \ && cp -r cppcheck.exe cfg/ $BOOTSTRAP/share/cppcheck \ && $BOOTSTRAP/bin/$ARCH-gcc -DEXE=../share/cppcheck/cppcheck.exe -DCMD=cppcheck \ -Os -fno-asynchronous-unwind-tables -Wl,--gc-sections -s -nostdlib \ -o $BOOTSTRAP/bin/cppcheck.exe $SOURCE_CODE/alias.c -lkernel32 - -#pack-up +# Pack up new executables cd $WORKDIR -cp -r $SOURCE_CODE/* $BOOTSTRAP/src #?? -printf "id ICON \"$BOOTSTRAP/src/gnu-windows.ico\"" >gnu-windows.rc \ +printf "id ICON \"$SOURCE_CODE/gnu-windows.ico\"" > gnu-windows.rc \ && $BOOTSTRAP/bin/$ARCH-windres -o gnu-windows.o gnu-windows.rc \ && $BOOTSTRAP/bin/$ARCH-gcc \ -Os -fno-asynchronous-unwind-tables \ @@ -477,6 +463,8 @@ printf "id ICON \"$BOOTSTRAP/src/gnu-windows.ico\"" >gnu-windows.rc \ cd $WORKDIR -cp $BOOTSTRAP/$ARCH/bin/libwinpthread-1.dll $BOOTSTRAP/bin/libwinpthread-1.dll +# Copy all executable from $ARCH into the primary folder +cp $BOOTSTRAP/$ARCH/bin/*.dll $BOOTSTRAP/bin +cp $BOOTSTRAP/$ARCH/bin/*.exe $BOOTSTRAP/bin echo -e -n "Build sucessfully. Your GNU Windows is under path: $BOOTSTRAP" diff --git a/.gnu-windows/src/.dirstamp b/.gnu-windows/src/.dirstamp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.gnu-windows/src/7z.mak b/.gnu-windows/src/7z.mak new file mode 100644 index 0000000000..149b8cf294 --- /dev/null +++ b/.gnu-windows/src/7z.mak @@ -0,0 +1,132 @@ +CROSS = x86_64-w64-mingw32- +CC = $(CROSS)gcc +CXX = $(CROSS)g++ +WINDRES = $(CROSS)windres +LDFLAGS = -mwindows -s -Wl,--gc-sections +LDLIBS = -lcomdlg32 -lole32 -loleaut32 -luuid +CFLAGS = -fno-ident -Oz \ + -DZ7_SFX \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_CRYPTO \ + -DZ7_NO_REGISTRY \ + -DZ7_NO_READ_FROM_CODER \ + +obj = \ + CPP/7zip/Bundles/SFXWin/resource.o \ + CPP/7zip/Bundles/SFXWin/SfxWin.o \ + CPP/7zip/UI/GUI/ExtractDialog.o \ + CPP/7zip/UI/GUI/ExtractGUI.o \ + CPP/Common/CRC.o \ + CPP/Common/CommandLineParser.o \ + CPP/Common/IntToString.o \ + CPP/Common/NewHandler.o \ + CPP/Common/MyString.o \ + CPP/Common/StringConvert.o \ + CPP/Common/MyVector.o \ + CPP/Common/Wildcard.o \ + CPP/Windows/Clipboard.o \ + CPP/Windows/CommonDialog.o \ + CPP/Windows/DLL.o \ + CPP/Windows/ErrorMsg.o \ + CPP/Windows/FileDir.o \ + CPP/Windows/FileFind.o \ + CPP/Windows/FileIO.o \ + CPP/Windows/FileName.o \ + CPP/Windows/MemoryGlobal.o \ + CPP/Windows/PropVariant.o \ + CPP/Windows/PropVariantConv.o \ + CPP/Windows/ResourceString.o \ + CPP/Windows/Shell.o \ + CPP/Windows/Synchronization.o \ + CPP/Windows/System.o \ + CPP/Windows/Window.o \ + CPP/Windows/Control/ComboBox.o \ + CPP/Windows/Control/Dialog.o \ + CPP/Windows/Control/ListView.o \ + CPP/7zip/Common/CreateCoder.o \ + CPP/7zip/Common/CWrappers.o \ + CPP/7zip/Common/FilePathAutoRename.o \ + CPP/7zip/Common/FileStreams.o \ + CPP/7zip/Common/InBuffer.o \ + CPP/7zip/Common/FilterCoder.o \ + CPP/7zip/Common/LimitedStreams.o \ + CPP/7zip/Common/OutBuffer.o \ + CPP/7zip/Common/ProgressUtils.o \ + CPP/7zip/Common/PropId.o \ + CPP/7zip/Common/StreamBinder.o \ + CPP/7zip/Common/StreamObjects.o \ + CPP/7zip/Common/StreamUtils.o \ + CPP/7zip/Common/VirtThread.o \ + CPP/7zip/UI/Common/ArchiveExtractCallback.o \ + CPP/7zip/UI/Common/ArchiveOpenCallback.o \ + CPP/7zip/UI/Common/DefaultName.o \ + CPP/7zip/UI/Common/Extract.o \ + CPP/7zip/UI/Common/ExtractingFilePath.o \ + CPP/7zip/UI/Common/LoadCodecs.o \ + CPP/7zip/UI/Common/OpenArchive.o \ + CPP/7zip/UI/Explorer/MyMessages.o \ + CPP/7zip/UI/FileManager/BrowseDialog.o \ + CPP/7zip/UI/FileManager/ComboDialog.o \ + CPP/7zip/UI/FileManager/ExtractCallback.o \ + CPP/7zip/UI/FileManager/FormatUtils.o \ + CPP/7zip/UI/FileManager/OverwriteDialog.o \ + CPP/7zip/UI/FileManager/PasswordDialog.o \ + CPP/7zip/UI/FileManager/ProgressDialog2.o \ + CPP/7zip/UI/FileManager/PropertyName.o \ + CPP/7zip/UI/FileManager/SysIconUtils.o \ + CPP/7zip/Archive/SplitHandler.o \ + CPP/7zip/Archive/Common/CoderMixer2.o \ + CPP/7zip/Archive/Common/ItemNameUtils.o \ + CPP/7zip/Archive/Common/MultiStream.o \ + CPP/7zip/Archive/Common/OutStreamWithCRC.o \ + CPP/7zip/Archive/7z/7zDecode.o \ + CPP/7zip/Archive/7z/7zExtract.o \ + CPP/7zip/Archive/7z/7zHandler.o \ + CPP/7zip/Archive/7z/7zIn.o \ + CPP/7zip/Archive/7z/7zRegister.o \ + CPP/7zip/Compress/Bcj2Coder.o \ + CPP/7zip/Compress/Bcj2Register.o \ + CPP/7zip/Compress/BcjCoder.o \ + CPP/7zip/Compress/BcjRegister.o \ + CPP/7zip/Compress/BranchMisc.o \ + CPP/7zip/Compress/BranchRegister.o \ + CPP/7zip/Compress/CopyCoder.o \ + CPP/7zip/Compress/CopyRegister.o \ + CPP/7zip/Compress/DeltaFilter.o \ + CPP/7zip/Compress/Lzma2Decoder.o \ + CPP/7zip/Compress/Lzma2Register.o \ + CPP/7zip/Compress/LzmaDecoder.o \ + CPP/7zip/Compress/LzmaRegister.o \ + CPP/7zip/Compress/PpmdDecoder.o \ + CPP/7zip/Compress/PpmdRegister.o \ + C/7zCrc.o \ + C/7zCrcOpt.o \ + C/7zStream.o \ + C/Alloc.o \ + C/Bcj2.o \ + C/Bra.o \ + C/Bra86.o \ + C/BraIA64.o \ + C/CpuArch.o \ + C/Delta.o \ + C/DllSecur.o \ + C/Lzma2Dec.o \ + C/Lzma2DecMt.o \ + C/LzmaDec.o \ + C/MtDec.o \ + C/Ppmd7.o \ + C/Ppmd7Dec.o \ + C/Sha256.o \ + C/Sha256Opt.o \ + C/Threads.o \ + +7z.sfx: $(obj) + $(CXX) $(LDFLAGS) -o $@ $(obj) $(LDLIBS) +clean: + rm -f 7z.sfx $(obj) +%.o: %.cpp + $(CXX) -c $(CFLAGS) -o $@ $^ +%.o: %.c + $(CC) -c $(CFLAGS) -o $@ $^ +%.o: %.rc + $(WINDRES) -o $@ $^ diff --git a/.gnu-windows/src/SHA256SUMS b/.gnu-windows/src/SHA256SUMS index 3503e1690d..025ee2100b 100644 --- a/.gnu-windows/src/SHA256SUMS +++ b/.gnu-windows/src/SHA256SUMS @@ -1,16 +1,15 @@ -ae9a5789e23459e59606e6714723f2d3ffc31c03174191ef0d015bdf06007450 binutils-2.41.tar.xz -e9376e8848200ca085bd74804861f2904e12916e836a23d53bb41d166d38ac0d busybox-w32-FRP-5236-g7dff7f376.tgz -785dcbf711048dfe43ae920b6eff2eeebb4a096e88188a40e173ca4c030f57c3 cppcheck-2.10.tar.gz +356071007360e5a1824d9904993e8b2480b51b570e8c9faf7c0f58ebe4bf9f74 7z2301-src.tar.xz +b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz +4b3a8ad7a1a6a95b1e8437d2b6ac7d18c9443a9c3d984ef64154d2a4a6558cb0 busybox-w32-FRP-5467-g9376eebd8.tgz 71229a73f25529c9e3dabb2cb7310c55405d31caee8e8a9ab5c71b2406d4005a ctags-6.0.0.tar.gz -ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe expat-2.5.0.tar.xz -e275e76442a6067341a27f04c5c6b83d8613144004c0413528863dc6b5c743da gcc-13.2.0.tar.xz -115ad5c18d69a6be2ab15882d365dda2a2211c14f480b3502c6eba576e2e95a0 gdb-13.1.tar.xz +ee14b4c5d8908b1bec37ad937607eab183d4d9806a08adee472c3c3121d27364 expat-2.6.2.tar.xz +a7b39bc69cbf9e25826c5a60ab26477001f7c08d85cec04bc0e29cabed6f3cc9 gcc-14.2.0.tar.xz +83350ccd35b5b5a0cba6b334c41294ea968158c573940904f00b92f76345314d gdb-15.2.tar.xz a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898 gmp-6.3.0.tar.xz 8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313 libiconv-1.17.tar.gz dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3 make-4.4.1.tar.gz -3f66bce069ee8bed7439a1a13da7cb91a5e67ea6170f21317ac7f5794625ee10 mingw-w64-v11.0.1.tar.bz2 +cc41898aac4b6e8dd5cffd7331b9d9515b912df4420a3a612b5ea2955bbeed2f mingw-w64-v12.0.0.tar.bz2 ab642492f5cf882b74aa0cb730cd410a81edcdbec895183ce930e706c1c759b8 mpc-1.3.1.tar.gz 277807353a6726978996945af13e52829e3abd7a9a5b7fb2793894e18f1fcbb2 mpfr-4.2.1.tar.xz -3caf6729c1073bf96629b57cee31eeb54f4f8129b01902c73428836550b30a3f nasm-2.15.05.tar.xz 590dbe0f5835f66992df096d3602d0271103f90cf8557a5d124f693c2b40d7ec PDCurses-3.9.tar.gz a6456bc154999d83d0c20d968ac7ba6e7df0d02f3cb6427fb248660bacfb336e vim-9.0.tar.bz2 diff --git a/.gnu-windows/src/alias.c b/.gnu-windows/src/alias.c index 91fc7b8028..995ec3e2e9 100644 --- a/.gnu-windows/src/alias.c +++ b/.gnu-windows/src/alias.c @@ -1,4 +1,4 @@ -// Well-behaved command line aliases for gnu-windows +// Well-behaved command line aliases for w64devkit // // Unlike batch script aliases, this program will not produce an annoying // and useless "Terminate batch job (Y/N)" prompt. When compiling, define @@ -38,6 +38,8 @@ typedef char16_t c16; typedef struct {} *handle; +typedef b32 __stdcall handler(i32); + typedef struct { u32 cb; uptr a, b, c; @@ -61,6 +63,7 @@ W32 i32 GetExitCodeProcess(handle, u32 *); W32 u32 GetFullPathNameW(c16 *, u32, c16 *, c16 *); W32 u32 GetModuleFileNameW(handle, c16 *, u32); W32 handle GetStdHandle(u32); +W32 b32 SetConsoleCtrlHandler(handler, b32); W32 byte *VirtualAlloc(byte *, usize, u32, u32); W32 b32 VirtualFree(byte *, usize, u32); W32 u32 WaitForSingleObject(handle, u32); @@ -68,7 +71,7 @@ W32 b32 WriteFile(handle, u8 *, u32, u32 *, void *); // Application -#define ERR(s) "gnu-windows (alias): " s "\n" +#define ERR(s) "w64devkit (alias): " s "\n" #define new(h, t, n) (t *)alloc(h, sizeof(t), alignof(t), n) __attribute((malloc)) @@ -156,6 +159,11 @@ static si *newstartupinfo(byte **heap) return s; } +static b32 __stdcall ignorectrlc(i32) +{ + return 1; +} + static i32 aliasmain(void) { byte *heap_start = VirtualAlloc(0, 1<<18, 0x3000, 4); @@ -204,6 +212,7 @@ static i32 aliasmain(void) si *si = newstartupinfo(&heap); pi pi; + SetConsoleCtrlHandler(ignorectrlc, 1); // NOTE: set as late a possible if (!CreateProcessW(exe->buf, cmd->buf, 0, 0, 1, 0, 0, 0, si, &pi)) { static const u8 msg[] = ERR("could not start process\n"); return fatal((u8 *)msg, lengthof(msg)); diff --git a/.gnu-windows/src/binutils-dlltool-zero-ordinals.patch b/.gnu-windows/src/binutils-dlltool-zero-ordinals.patch new file mode 100644 index 0000000000..594c123702 --- /dev/null +++ b/.gnu-windows/src/binutils-dlltool-zero-ordinals.patch @@ -0,0 +1,19 @@ +If the .def author does not pick an ordinal then it has no meaningful +ordinal. It never makes sense to invent unique hints. That populates +binaries with noise and inhibits reproducible builds. Instead zero out +all ordinal hints to indicate "no data." In Mingw-w64 import libraries +virtually every implicit ordinal hint is nonsense. + +The singular exception might be ordinal-only, NONAME entries without an +explicit ordinal, which instead relies on implicit, undocumented ordinal +numbering. In that case it's not merely a hint. However, this is unsound +and ought to be a syntax error, as it is with MSVC lib.exe. (This patch +should probably make it so.) I could find no examples of this situation +in the wild. + +--- a/binutils/dlltool.c ++++ b/binutils/dlltool.c +@@ -3690,2 +3690,3 @@ fill_ordinals (export_type **d_export_vec) + done:; ++ d_export_vec[i]->ordinal = 0; + } diff --git a/.gnu-windows/src/busybox-000-disable-beep.patch b/.gnu-windows/src/busybox-000-disable-beep.patch index 9c6a17bae6..39465842f6 100644 --- a/.gnu-windows/src/busybox-000-disable-beep.patch +++ b/.gnu-windows/src/busybox-000-disable-beep.patch @@ -1,10 +1,8 @@ --- a/libbb/lineedit.c +++ b/libbb/lineedit.c -@@ -488,7 +488,6 @@ - +@@ -537,5 +537,4 @@ static void beep(void) { -- bb_putchar('\007'); +- bb_putchar_stderr('\007'); } - /* Full or last/sole prompt line, reset edit cursor, calculate terminal cursor. diff --git a/.gnu-windows/src/busybox-002-default-noiconify.patch b/.gnu-windows/src/busybox-002-default-noiconify.patch new file mode 100644 index 0000000000..72166cdea1 --- /dev/null +++ b/.gnu-windows/src/busybox-002-default-noiconify.patch @@ -0,0 +1,8 @@ +--- a/shell/ash.c ++++ b/shell/ash.c +@@ -12686,4 +12686,5 @@ + #if ENABLE_ASH_NOCONSOLE + noconsole = console_state(); ++ noiconify = 1; + #endif + if (login_sh != NULL) { /* if we came from startup code */ diff --git a/.gnu-windows/src/busybox-004-system-profile.patch b/.gnu-windows/src/busybox-004-system-profile.patch new file mode 100644 index 0000000000..0b930f9265 --- /dev/null +++ b/.gnu-windows/src/busybox-004-system-profile.patch @@ -0,0 +1,7 @@ +--- a/shell/ash.c ++++ b/shell/ash.c +@@ -16349,3 +16349,3 @@ + +- hp = exe_relative_path("/etc/profile"); ++ hp = exe_relative_path("../src/profile"); + read_profile(hp); diff --git a/.gnu-windows/src/busybox-005-disable-utf8-check.patch b/.gnu-windows/src/busybox-005-disable-utf8-check.patch new file mode 100644 index 0000000000..266c47e6cb --- /dev/null +++ b/.gnu-windows/src/busybox-005-disable-utf8-check.patch @@ -0,0 +1,8 @@ +Allow it to run without UTF-8 support, such as on Windows 7. +--- a/libbb/appletlib.c ++++ b/libbb/appletlib.c +@@ -1331,3 +1331,3 @@ + #if ENABLE_PLATFORM_MINGW32 +-# if ENABLE_FEATURE_UTF8_MANIFEST ++# if ENABLE_FEATURE_UTF8_MANIFEST && 0 + if (GetACP() != CP_UTF8) { diff --git a/.gnu-windows/src/busybox-alias.c b/.gnu-windows/src/busybox-alias.c index 60ed0ad024..5c23fd895e 100644 --- a/.gnu-windows/src/busybox-alias.c +++ b/.gnu-windows/src/busybox-alias.c @@ -19,6 +19,8 @@ typedef char16_t c16; typedef struct {} *handle; +typedef b32 __stdcall handler(i32); + typedef struct { u32 cb; void *a, *b, *c; @@ -41,6 +43,7 @@ W32 c16 *GetCommandLineW(void); W32 i32 GetExitCodeProcess(handle, u32 *); W32 u32 GetModuleFileNameW(handle, c16 *, u32); W32 handle GetStdHandle(u32); +W32 b32 SetConsoleCtrlHandler(handler, b32); W32 u32 WaitForSingleObject(handle, u32); W32 b32 WriteFile(handle, u8 *, u32, u32 *, void *); @@ -84,6 +87,11 @@ static u32 fatal(u8 *msg, i32 len) return 0x17e; } +static b32 __stdcall ignorectrlc(i32) +{ + return 1; +} + static u32 run(void) { c16 buf[MAX_PATH]; @@ -95,7 +103,7 @@ static u32 run(void) c16 busybox[] = u"\\busybox.exe"; append(&exe, busybox, countof(busybox)); if (exe.err) { - static u8 msg[] = "gnu-windows: busybox.exe path too long\n"; + static u8 msg[] = "w64devkit: busybox.exe path too long\n"; return fatal(msg, lengthof(msg)); } @@ -103,8 +111,9 @@ static u32 run(void) si.cb = sizeof(si); pi pi; c16 *cmdline = GetCommandLineW(); + SetConsoleCtrlHandler(ignorectrlc, 1); // NOTE: set as late a possible if (!CreateProcessW(exe.buf, cmdline, 0, 0, 1, 0, 0, 0, &si, &pi)) { - static u8 msg[] = "gnu-windows: could not start busybox.exe\n"; + static u8 msg[] = "w64devkit: could not start busybox.exe\n"; return fatal(msg, lengthof(msg)); } diff --git a/.gnu-windows/src/gcc-stdcall-align.patch b/.gnu-windows/src/gcc-stdcall-align.patch new file mode 100644 index 0000000000..e69fe8337e --- /dev/null +++ b/.gnu-windows/src/gcc-stdcall-align.patch @@ -0,0 +1,33 @@ +GCC requires 16-byte stack alignment on function entry, but x86 calling +conventions only provide 4-byte alignment. Entry points, such as thread +procedures, callbacks, and process entry points, are likely called on a +stack not aligned to GCC's expectations. Since these functions are also +typically __stdcall, silently add the force_align_arg_pointer attribute +to such functions so that GCC re-aligns the stack. + +Note: main, wmain, WinMain, and wWinMain are special cases which the CRT +calls with 16-byte alignment. + +--- a/gcc/config/i386/i386-options.cc ++++ b/gcc/config/i386/i386-options.cc +@@ -3689,7 +3689,7 @@ + arguments as in struct attribute_spec.handler. */ + + static tree +-ix86_handle_cconv_attribute (tree *node, tree name, tree args, int, ++ix86_handle_cconv_attribute (tree *node, tree name, tree args, int flags, + bool *no_add_attrs) + { + if (TREE_CODE (*node) != FUNCTION_TYPE +@@ -3773,6 +3773,11 @@ + sseregparm. */ + else if (is_attribute_p ("stdcall", name)) + { ++ if (!TARGET_64BIT) ++ { ++ tree attr = tree_cons (get_identifier ("force_align_arg_pointer"), NULL_TREE, NULL_TREE); ++ decl_attributes (node, attr, flags); ++ } + if (lookup_attribute ("cdecl", TYPE_ATTRIBUTES (*node))) + { + error ("stdcall and cdecl attributes are not compatible"); diff --git a/.gnu-windows/src/gdb-000-alternate-main.patch b/.gnu-windows/src/gdb-000-alternate-main.patch index c7b1c5da6b..402b07b065 100644 --- a/.gnu-windows/src/gdb-000-alternate-main.patch +++ b/.gnu-windows/src/gdb-000-alternate-main.patch @@ -1,10 +1,10 @@ --- a/gdb/symtab.c +++ b/gdb/symtab.c -@@ -6300,6 +6300,23 @@ +@@ -6537,6 +6537,23 @@ if (symbol_found_p) return; -+ if (gdbarch_osabi (target_gdbarch ()) == GDB_OSABI_WINDOWS) ++ if (gdbarch_osabi (current_inferior ()->arch ()) == GDB_OSABI_WINDOWS) + { + static const char *const mains[] = { + "WinMain", "wWinMain", "main", "wmain", "WinMainCRTStartup", "mainCRTStartup" @@ -15,12 +15,12 @@ + msym = lookup_minimal_symbol (main, NULL, NULL); + if (msym.minsym != NULL) + { -+ set_main_name (main, language_unknown); ++ set_main_name (pspace, main, language_unknown); + return; + } + } + } + - set_main_name ("main", language_unknown); + set_main_name (pspace, "main", language_unknown); } diff --git a/.gnu-windows/src/gdb-001-confirm-off.patch b/.gnu-windows/src/gdb-001-confirm-off.patch index 48328481ed..9bb9f2197f 100644 --- a/.gnu-windows/src/gdb-001-confirm-off.patch +++ b/.gnu-windows/src/gdb-001-confirm-off.patch @@ -3,7 +3,7 @@ creates substantial friction and should be off by default. --- a/gdb/top.c +++ b/gdb/top.c -@@ -132,3 +132,3 @@ +@@ -133,3 +133,3 @@ -bool confirm = true; +bool confirm = false; diff --git a/.gnu-windows/src/gnu-windows.c b/.gnu-windows/src/gnu-windows.c deleted file mode 100644 index 97ce0d525b..0000000000 --- a/.gnu-windows/src/gnu-windows.c +++ /dev/null @@ -1,552 +0,0 @@ -// Tiny, standalone launcher for gnu_windows -// * Sets $gnu_windows to the release version (-DVERSION) -// * Sets $gnu_windows_HOME to the install location -// * Maybe sets $HOME according to gnu_windows.ini -// * Starts a login shell with "sh -l" -// -// $ gcc -DVERSION="$VERSION" -nostartfiles -fno-builtin -// -o gnu_windows.exe gnu_windows.c -// -// This is free and unencumbered software released into the public domain. - -#define sizeof(a) (size)(sizeof(a)) -#define alignof(a) (size)(_Alignof(a)) -#define countof(a) (sizeof(a) / sizeof(*(a))) -#define lengthof(s) (countof(s) - 1) - -#define new(...) newx(__VA_ARGS__, new4, new3, new2)(__VA_ARGS__) -#define newx(a,b,c,d,e,...) e -#define new2(a, t) (t *)alloc(a, sizeof(t), alignof(t), 1, 0) -#define new3(a, t, n) (t *)alloc(a, sizeof(t), alignof(t), n, 0) -#define new4(a, t, n, f) (t *)alloc(a, sizeof(t), alignof(t), n, f) - -typedef unsigned char byte; -typedef __UINT8_TYPE__ u8; -typedef unsigned short u16; -typedef signed int i32; -typedef signed int b32; -typedef unsigned int u32; -typedef __UINTPTR_TYPE__ uptr; -typedef __PTRDIFF_TYPE__ size; -typedef __SIZE_TYPE__ usize; -typedef unsigned short char16_t; // for GDB -typedef char16_t c16; - -typedef struct {} *handle; - -typedef struct { - u32 cb; - uptr a, b, c; - i32 d, e, f, g, h, i, j, k; - u16 l, m; - uptr n, o, p, q; -} si; - -typedef struct { - handle process; - handle thread; - u32 pid; - u32 tid; -} pi; - -#define MAX_PATH 260 -#define MAX_ENVVAR 32767 -#define MAX_CMDLINE 32767 -#define MAX_INI (1<<18) -#define CP_UTF8 65001 -#define PAGE_READWRITE 0x04 -#define MEM_COMMIT 0x1000 -#define MEM_RESERVE 0x2000 -#define MEM_RELEASE 0x8000 -#define GENERIC_READ 0x80000000 -#define OPEN_EXISTING 3 -#define FILE_SHARE_ALL 7 - -#define W32 __attribute((dllimport,stdcall)) -W32 b32 CloseHandle(handle); -W32 handle CreateFileW(c16 *, u32, u32, void *, u32, u32, handle); -W32 b32 CreateProcessW(c16*,c16*,void*,void*,i32,u32,c16*,c16*,si*,pi*); -W32 void ExitProcess(u32) __attribute((noreturn)); -W32 u32 ExpandEnvironmentStringsW(c16 *, c16 *, u32); -W32 u32 GetEnvironmentVariableW(c16 *, c16 *, u32); -W32 i32 GetExitCodeProcess(handle, u32 *); -W32 u32 GetFullPathNameW(c16 *, u32, c16 *, c16 *); -W32 u32 GetModuleFileNameW(handle, c16 *, u32); -W32 i32 MessageBoxW(handle, c16 *, c16 *, u32); -W32 i32 MultiByteToWideChar(u32, u32, u8 *, i32, c16 *, i32); -W32 b32 ReadFile(handle, u8 *, u32, u32 *, void *); -W32 b32 SetConsoleTitleW(c16 *); -W32 b32 SetCurrentDirectoryW(c16 *); -W32 b32 SetEnvironmentVariableW(c16 *, c16 *); -W32 byte *VirtualAlloc(byte *, usize, u32, u32); -W32 b32 VirtualFree(byte *, usize, u32); -W32 u32 WaitForSingleObject(handle, u32); - -#define S(s) (s8){(u8 *)s, lengthof(s)} -typedef struct { - u8 *s; - size len; -} s8; - -#define U(s) (s16){s, lengthof(s)} -typedef struct { - c16 *s; - size len; -} s16; - -static s8 s8span(u8 *beg, u8 *end) -{ - s8 s = {}; - s.s = beg; - s.len = end - beg; - return s; -} - -static b32 s8equals(s8 a, s8 b) -{ - if (a.len != b.len) { - return 0; - } - for (size i = 0; i < a.len; i++) { - if (a.s[i] != b.s[i]) { - return 0; - } - } - return 1; -} - -static void fatal(c16 *msg) -{ - MessageBoxW(0, msg, u"gnu_windows launcher", 0x10); - ExitProcess(2); -} - -typedef struct { - byte *mem; - size cap; - size off; -} arena; - -static arena *newarena(size cap) -{ - arena *a = 0; - byte *mem = VirtualAlloc(0, cap, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); - if (mem) { - a = (arena *)mem; - a->mem = mem; - a->cap = cap; - a->off = sizeof(arena); - } - return a; -} - -static void freearena(arena *a) -{ - VirtualFree(a->mem, 0, MEM_RELEASE); -} - -#define NOZERO (1<<0) -#define SOFTFAIL (1<<1) -__attribute((malloc)) -__attribute((alloc_align(3))) -__attribute((alloc_size(2, 4))) -static byte *alloc(arena *a, size objsize, size align, size count, i32 flags) -{ - size avail = a->cap - a->off; - size pad = -a->off & (align - 1); - if (count > (avail - pad)/objsize) { - if (flags & SOFTFAIL) { - return 0; - } - fatal(u"Out of memory"); - } - size total = count*objsize; - byte *p = a->mem + a->off + pad; - if (!(flags & NOZERO)) { - for (size i = 0; i < total; i++) { - p[i] = 0; - } - } - a->off += pad + total; - return p; -} - -static arena splitarena(arena *a, i32 div) -{ - size avail = a->cap - a->off; - size cap = avail / div; - arena sub = {}; - sub.mem = alloc(a, 1, 32, cap, NOZERO); - sub.cap = cap; - return sub; -} - -typedef enum { - INI_eof, - INI_section, - INI_key, - INI_value -} initype; - -typedef struct { - s8 name; - initype type; -} initoken; - -typedef struct { - u8 *beg; - u8 *end; - b32 invalue; -} iniparser; - -static b32 inidone(iniparser *p) -{ - return p->beg == p->end; -} - -static u8 ininext(iniparser *p) -{ - return *p->beg++; -} - -static b32 iniblank(u8 c) -{ - return c==' ' || c=='\t' || c=='\r'; -} - -static void iniskip(iniparser *p) -{ - for (; !inidone(p) && iniblank(*p->beg); p->beg++) {} -} - -static u8 *initok(iniparser *p, u8 term) -{ - u8 *end = p->beg; - while (!inidone(p)) { - u8 c = ininext(p); - if (c == term) { - return end; - } else if (c == '\n') { - break; - } else if (!iniblank(c)) { - end = p->beg; - } - } - return term=='\n' ? end : 0; -} - -static b32 iniquoted(s8 s) -{ - return s.len>1 && s.s[0]=='"' && s.s[s.len-1]=='"'; -} - -// Parses like GetPrivateProfileString except sections cannot contain -// newlines. Invalid input lines are ignored. Comment lines begin with -// ';' following any whitespace. No trailing comments. Trims leading and -// trailing whitespace from sections, keys, and values. To preserve -// whitespace, values may be double-quoted. No quote escapes. Content on -// a line following a closing section ']' is ignored. Token encoding -// matches input encoding. An INI_value always follows an INI_key key. -static initoken iniparse(iniparser *p) -{ - initoken r = {}; - - if (p->invalue) { - p->invalue = 0; - iniskip(p); - u8 *beg = p->beg; - u8 *end = initok(p, '\n'); - r.name = s8span(beg, end); - r.type = INI_value; - if (iniquoted(r.name)) { - r.name.s++; - r.name.len -= 2; - } - return r; - } - - for (;;) { - iniskip(p); - if (inidone(p)) { - return r; - } - - u8 *end; - u8 *beg = p->beg; - switch (ininext(p)) { - case ';': - while (!inidone(p) && ininext(p)!='\n') {} - break; - - case '[': - iniskip(p); - beg = p->beg; - end = initok(p, ']'); - if (end) { - // skip over anything else on the line - while (!inidone(p) && ininext(p)!='\n') {} - r.name = s8span(beg, end); - r.type = INI_section; - return r; - } - break; - - case '\n': - break; - - default: - end = initok(p, '='); - if (end) { - p->invalue = 1; - r.name = s8span(beg, end); - r.type = INI_key; - return r; - } - } - } -} - -typedef enum { - sym_null = 0, - sym_gnu_windows, - sym_home, -} symbol; - -static symbol intern(s8 s) -{ - static struct { - s8 name; - symbol symbol; - } symbols[] = { - {S("gnu_windows"), sym_gnu_windows}, - {S("home"), sym_home}, - }; - for (size i = 0; i < countof(symbols); i++) { - if (s8equals(symbols[i].name, s)) { - return symbols[i].symbol; - } - } - return sym_null; -} - -static u8 *makecstr(arena *a, s8 s) -{ - u8 *r = new(a, u8, s.len+1); - for (size i = 0; i < s.len; i++) { - r[i] = s.s[i]; - } - return r; -} - -// Read and process "gnu_windows.home" from "gnu_windows.ini". Environment -// variables are expanded, and if relative, the result is converted into -// an absolute path. Returns null on error. -// -// Before calling, the current working directory must be changed to the -// location of gnu_windows.exe. -static c16 *homeconfig(arena *perm, arena scratch) -{ - handle h = CreateFileW( - u"gnu_windows.ini", - GENERIC_READ, - FILE_SHARE_ALL, - 0, - OPEN_EXISTING, - 0, - 0 - ); - if (h == (handle)-1) { - return 0; - } - - iniparser *p = new(&scratch, iniparser); - p->beg = new(&scratch, u8, MAX_INI, NOZERO); - u32 inilen; - b32 r = ReadFile(h, p->beg, MAX_INI, &inilen, 0); - CloseHandle(h); - if (!r || inilen == MAX_INI) { - return 0; - } - p->end = p->beg + inilen; - - u8 *home = 0; - u32 len = 0; - for (symbol section = 0, key = 0;;) { - initoken t = iniparse(p); - switch (t.type) { - case INI_eof: - break; - case INI_section: - section = intern(t.name); - continue; - case INI_key: - key = intern(t.name); - continue; - case INI_value: - if (!home && section==sym_gnu_windows && key==sym_home) { - home = makecstr(&scratch, t.name); - len = (u32)(t.name.len + 1); // include terminator - } - continue; - } - break; - } - - c16 *whome = new(&scratch, c16, len); - if (!MultiByteToWideChar(CP_UTF8, 0, home, len, whome, len)) { - return 0; - } - - // Process INI string into a final HOME path. Allocate a bit more - // than MAX_PATH, because GetFullPathNameW could technically reduce - // it to within MAX_PATH if there are lots of relative components. - u32 cap = MAX_PATH*4; - c16 *expanded = new(&scratch, c16, cap); - len = ExpandEnvironmentStringsW(whome, expanded, cap); - if (!len || len>cap) { - return 0; - } - - // The final result must fit within MAX_PATH in order to be useful. - c16 *path = new(perm, c16, MAX_PATH); - len = GetFullPathNameW(expanded, MAX_PATH, path, 0); - if (!len || len>=MAX_PATH) { - return 0; - } - return path; -} - -typedef struct { - c16 *buf; - size cap; - size len; - b32 err; -} buf16; - -static buf16 newbuf16(arena *a, size cap) -{ - buf16 buf = {}; - buf.buf = new(a, c16, cap, NOZERO); - buf.cap = cap; - return buf; -} - -static void buf16cat(buf16 *buf, s16 s) -{ - size avail = buf->cap - buf->len; - size count = s.lenbuf + buf->len; - for (size i = 0; i < count; i++) { - dst[i] = s.s[i]; - } - buf->len += count; - buf->err |= count < s.len; -} - -static void buf16c16(buf16 *buf, c16 c) -{ - s16 s = {&c, 1}; - buf16cat(buf, s); -} - -static void buf16moduledir(buf16 *buf, arena scratch) -{ - c16 *path = new(&scratch, c16, MAX_PATH); - size len = GetModuleFileNameW(0, path, MAX_PATH); - for (; len; len--) { - switch (path[len-1]) { - case '/': - case '\\': buf16cat(buf, (s16){path, len-1}); - return; - } - } -} - -static void buf16getenv(buf16 *buf, c16 *key, arena scratch) -{ - s16 var = {}; - var.s = new(&scratch, c16, MAX_ENVVAR, NOZERO); - u32 len = GetEnvironmentVariableW(key, var.s, MAX_ENVVAR); - var.len = len>=MAX_ENVVAR ? 0 : len; - buf16cat(buf, var); -} - -static void toslashes(c16 *path) -{ - for (size i = 0; i < path[i]; i++) { - path[i] = path[i]=='\\' ? '/' : path[i]; - } -} - -static u32 gnu_windows(void) -{ - arena *perm = newarena(1<<22); - if (!perm) { - fatal(u"Out of memory on startup"); - } - arena scratch = splitarena(perm, 2); - - // First load the module directory into the fresh buffer, and use it - // for a few different operations. - buf16 path = newbuf16(perm, MAX_ENVVAR); - buf16moduledir(&path, scratch); - buf16 moduledir = path; // to truncate back to the module dir - - buf16c16(&path, 0); // null terminator - SetEnvironmentVariableW(u"gnu_windows_HOME", path.buf); // ignore errors - - // Maybe set HOME from gnu_windows.ini - if (SetCurrentDirectoryW(path.buf)) { - c16 *home = homeconfig(perm, scratch); - if (home) { - toslashes(home); - SetEnvironmentVariableW(u"HOME", home); // ignore errors - } - } - - // Continue building PATH - path = moduledir; - buf16cat(&path, U(u"\\bin;")); - buf16getenv(&path, u"PATH", scratch); - buf16c16(&path, 0); // null terminator - if (path.err || !SetEnvironmentVariableW(u"PATH", path.buf)) { - fatal(u"Failed to configure $PATH"); - } - - #ifdef VERSION - #define LSTR(s) XSTR(s) - #define XSTR(s) u ## # s - SetEnvironmentVariableW(u"gnu_windows", LSTR(VERSION)); // ignore errors - #endif - - // Set the console title as late as possible, but not after starting - // the shell because .profile might change it. - SetConsoleTitleW(u"gnu_windows"); // ignore errors - - path = moduledir; - buf16cat(&path, U(u"\\bin\\busybox.exe")); - buf16c16(&path, 0); // null terminator - - // Start a BusyBox login shell - si si = {}; - si.cb = sizeof(si); - pi pi; - c16 cmdline[] = u"sh -l"; // NOTE: must be mutable! - if (!CreateProcessW(path.buf, cmdline, 0, 0, 1, 0, 0, 0, &si, &pi)) { - fatal(u"Failed to launch a login shell"); - } - - // Wait for shell to exit - freearena(perm); - u32 ret; - WaitForSingleObject(pi.process, -1); - GetExitCodeProcess(pi.process, &ret); - return ret; -} - -__attribute((force_align_arg_pointer)) -void mainCRTStartup(void) -{ - u32 r = gnu_windows(); - ExitProcess(r); -} diff --git a/.gnu-windows/src/gnu-windows.ico b/.gnu-windows/src/gnu-windows.ico index 6de3a36f51..b68828d1a9 100644 Binary files a/.gnu-windows/src/gnu-windows.ico and b/.gnu-windows/src/gnu-windows.ico differ diff --git a/.gnu-windows/src/gnu-windows.ini b/.gnu-windows/src/gnu-windows.ini index bec644d0d4..78c18c5484 100644 --- a/.gnu-windows/src/gnu-windows.ini +++ b/.gnu-windows/src/gnu-windows.ini @@ -1,7 +1,6 @@ -; Win-GDevKit.ini -- configuration for Win-GDevKit.exe -; This file must be encoded as UTF-8. +; w64devkit.ini -- configuration for w64devkit.exe -[Win-GDevKit] +[w64devkit] ; home: Sets the HOME environment variable for the shell. Place a ; .profile in this directory to do further environment configuration ; using the shell itself. @@ -11,8 +10,9 @@ ; la ExpandEnvironmentStrings, and a relative path will be converted to ; an absolute path relative to this .ini file. ; -; Known bugs: busybox-w32 uses the narrow API and does not support wide -; paths, so be mindful when using non-ASCII paths for this value. +; title: Sets the initial title for the new console window. This value +; may also contain environment variables. ; ;home = ..\home ;home = %HOMEDRIVE%%HOMEPATH% +;title = %USERNAME%@%COMPUTERNAME% [%W64DEVKIT_HOME% %W64DEVKIT%] diff --git a/.gnu-windows/src/libchkstk.S b/.gnu-windows/src/libchkstk.S index 55be31f188..5016c82ae1 100644 --- a/.gnu-windows/src/libchkstk.S +++ b/.gnu-windows/src/libchkstk.S @@ -29,10 +29,11 @@ __chkstk: # endif push %rax push %rcx + mov %gs:(0x10), %rcx // rcx = stack low address neg %rax // rax = frame low address add %rsp, %rax // " - mov %gs:(0x10), %rcx // rcx = stack low address - jmp 1f + jb 1f // frame low address overflow? + xor %eax, %eax // overflowed: frame low address = null 0: sub $0x1000, %rcx // extend stack into guard page test %eax, (%rcx) // commit page (two instruction bytes) 1: cmp %rax, %rcx @@ -49,15 +50,15 @@ __chkstk: ___chkstk_ms: push %eax push %ecx + mov %fs:(0x08), %ecx // ecx = stack low address neg %eax // eax = frame low address add %esp, %eax // " - mov %fs:(0x08), %ecx // ecx = stack low address - jmp 1f + jb 1f // frame low address overflow? + xor %eax, %eax // overflowed: frame low address = null 0: sub $0x1000, %ecx // extend stack into guard page test %eax, (%ecx) // commit page (two instruction bytes) 1: cmp %eax, %ecx ja 0b - sub %esp, %eax pop %ecx pop %eax ret @@ -67,10 +68,12 @@ ___chkstk_ms: .globl __chkstk __chkstk: push %ecx // preserve ecx + mov %fs:(0x08), %ecx // ecx = stack low address neg %eax // eax = frame low address lea 8(%esp,%eax), %eax // " - mov %fs:(0x08), %ecx // ecx = stack low address - jmp 1f + cmp %esp, %eax // frame low address overflow? + jb 1f // " + xor %eax, %eax // overflowed: frame low address = null 0: sub $0x1000, %ecx // extend stack into guard page test %eax, (%ecx) // commit page (two instruction bytes) 1: cmp %eax, %ecx diff --git a/.gnu-windows/src/peports.c b/.gnu-windows/src/peports.c new file mode 100644 index 0000000000..36995e38ba --- /dev/null +++ b/.gnu-windows/src/peports.c @@ -0,0 +1,790 @@ +// PE export/import table listing +// +// $ peports c:/windows/system32/kernel32.dll +// $ peports -i main.exe >imports.txt +// $ peports -e library.dll >exports.txt +// +// Compilation requires GCC or Clang. Behaves like "dumpbin /exports" +// and "dumpbin /imports" from MSVC, but open source, standalone, and +// much faster. For C++ symbols, consider piping output through c++filt +// or vc++filt. +// +// Dynamic linking only permits ASCII for module and symbol names, and +// both the MSVC and GNU toolchains sometimes choke on non-ASCII names. +// Therefore wide console output is unnecessary. All standard output is +// ASCII, and non-ASCII name bytes are escape-printed. Angle brackets +// in names are escaped, so brackets appearing in output are delimiters. +// Command line argument paths may be wide, though, since these are not +// so restricted. +// +// THIS IS NOT A SECURITY TOOL! While this program does not misbehave +// given arbitrary, untrusted input, its interpretation of PE data may +// not precisely match the Windows loader, which is itself inconsistent +// between releases. For specially-crafted inputs, outputs may differ +// from Windows' parsing of the same input. This is first and foremost a +// debugging tool. +// +// Porting note: The platform layer implements osload and oswrite. To +// run the application, it calls peports with command line arguments and +// a scratch arena. The application calls osload and oswrite as needed +// for reading file and writing output. +// +// Roadmap: +// * Alternate format options? (e.g. DEF, a better gendef) +// * A recursive option, to behave like a dependency walker? +// * An option to automatically demangle C++ symbols? +// +// This is free and unencumbered software released into the public domain. + +#define assert(c) while (!(c)) __builtin_unreachable() +#define countof(a) (iz)(sizeof(a) / sizeof(*(a))) +#define new(a, n, t) (t *)alloc(a, n, sizeof(t), _Alignof(t)) +#define s8(s) (s8){(u8 *)s, countof(s)-1} +#define catch(e) __builtin_setjmp((e)->jmp) + +typedef unsigned char u8; +typedef unsigned short u16; +typedef signed int b32; +typedef signed int i32; +typedef unsigned int u32; +typedef unsigned short char16_t; +typedef char16_t c16; +typedef __PTRDIFF_TYPE__ iz; +typedef __SIZE_TYPE__ uz; +typedef char byte; + +typedef struct { + u8 *data; + iz len; +} s8; + +typedef struct { + void *jmp[5]; + s8 err; +} escape; + +__attribute((noreturn)) +static void throw(escape *e, s8 reason) +{ + e->err = reason; + __builtin_longjmp(e->jmp, 1); +} + +typedef struct { + byte *beg; + byte *end; + escape *esc; +} arena; + +// Read an entire file into memory. Returns a null string on error. The +// information we care about is probably at the very beginning of the +// file, so this function does not fill the whole arena but truncates as +// necessary to leave an 8th or so of the remaining space for parsing. +// Truncation is not an error. The special path "-" is standard input. +static s8 osload(arena *, s8); + +// Write some bytes to standard output (1) or standard error (2). +static b32 oswrite(i32, u8 *, i32); + +static byte *alloc(arena *a, iz count, iz size, iz align) +{ + assert(count >= 0); + iz pad = -(uz)a->beg & (align - 1); + if (count >= (a->end - a->beg - pad)/size) { + throw(a->esc, s8("out of memory")); + } + byte *r = a->beg + pad; + a->beg += pad + count*size; + return __builtin_memset(r, 0, count*size); +} + +static s8 span(u8 *beg, u8 *end) +{ + assert(beg <= end); + s8 r = {0}; + r.data = beg; + r.len = end - beg; + return r; +} + +static b32 equals(s8 a, s8 b) +{ + if (a.len != b.len) { + return 0; + } + for (iz i = 0; i < a.len; i++) { + if (a.data[i] != b.data[i]) { + return 0; + } + } + return 1; +} + +static s8 slice3(s8 s, iz beg, iz end, escape *e) +{ + if (beg<0 || beg>end || end>s.len) { + throw(e, s8("unexpected end of input (slice)")); + } + s.data += beg; + s.len = end - beg; + return s; +} + +static s8 slice2(s8 s, iz beg, escape *e) +{ + return slice3(s, beg, s.len, e); +} + +static u16 readu16(s8 s, iz off, escape *e) +{ + if (off > s.len-2) { + throw(e, s8("unexpected end of input (uint16)")); + } + u8 *p = s.data + off; + return (u16)((u16)p[1]<<8 | p[0]); +} + +static u32 readu32(s8 s, iz off, escape *e) +{ + if (off > s.len-4) { + throw(e, s8("unexpected end of input (uint32)")); + } + u8 *p = s.data + off; + return (u32)p[3]<<24 | (u32)p[2]<<16 | (u32)p[1]<<8 | p[0]; +} + +static s8 nullterm(s8 s) +{ + if (!s.data) return s; + iz len = 0; + for (; lenerr && b->len) { + b->err |= !oswrite(b->fd, b->buf, b->len); + b->len = 0; + } +} + +static void print(u8buf *b, s8 s) +{ + for (iz off = 0; !b->err && offcap - b->len; + i32 count = availbuf+b->len, s.data+off, count); + off += count; + b->len += count; + if (b->len == b->cap) { + flush(b); + } + } +} + +static void printu32(u8buf *b, u32 x) +{ + u8 buf[16]; + u8 *end = buf + countof(buf); + u8 *beg = end; + do { + *--beg = (u8)(x%10) + '0'; + } while (x /= 10); + return print(b, span(beg, end)); +} + +// Escape-print a module or symbol name. +static void printname(u8buf *b, s8 s) +{ + if (!s.len) { + print(b, s8("<>")); // signify empty using brackets + } else { + for (iz i = 0; i < s.len; i++) { + u8 c = s.data[i]; + if (c<0x20 || c>0x7f || c=='<' || c=='>' || c=='\\') { + u8 encode[4] = "\\x.."; + encode[2] = "0123456789abcdef"[c>>4]; + encode[3] = "0123456789abcdef"[c&15]; + print(b, span(encode, encode+countof(encode))); + } else { + print(b, span(&c, &c+1)); + } + } + } +} + +typedef struct { + u32 beg; + u32 end; + s8 mem; +} region; + +typedef struct { + region *regions; + i32 len; +} vm; + +// Parsing a PE means simulating a loader. A virtual memory (vm) object +// represents sections mapped into a virtual address space, and this +// function reads regions out of that address space. +static s8 loadrva(vm m, u32 vaddr, escape *e) +{ + s8 r = {0}; + for (i32 i = 0; i < m.len; i++) { + region s = m.regions[i]; + if (vaddr>=s.beg && vaddr 0xffffffff-b) { + throw(e, s8("overflow computing 32-bit offset")); + } + return a + b; +} + +static void usage(u8buf *b) +{ + print(b, s8( + "usage: peports [-ehi] [files...]\n" + " -e print the export table\n" + " -h print this message\n" + " -i print the import table\n" + "Prints export and import tables of EXEs and DLLs.\n" + "Given no arguments, reads data from standard input.\n" + )); +} + +typedef struct { + u8buf *out; + u8buf *err; + i32 optind; + b32 exports; + b32 imports; +} config; + +enum {OPT_OK, OPT_EXIT, OPT_ERR}; + +static i32 parseopts(config *c, i32 argc, s8 *argv) +{ + for (c->optind = !!argc; c->optind < argc; c->optind++) { + s8 arg = argv[c->optind]; + if (!arg.len || arg.data[0]!='-') break; + for (iz i = 1; i < arg.len; i++) { + u8 x = arg.data[i]; + switch (x) { + case 'e': + c->exports = 1; + break; + case 'h': + usage(c->out); + flush(c->out); + return c->out->err ? OPT_ERR : OPT_EXIT; + case 'i': + c->imports = 1; + break; + default: + print(c->err, s8("peports: unknown option: -")); + print(c->err, span(&x, &x+1)); + print(c->err, s8("\n")); + usage(c->err); + flush(c->err); + return OPT_ERR; + } + } + } + + if (!c->imports && !c->exports) { + c->imports = c->exports = 1; + } + return OPT_OK; +} + +static void processpe(s8 dll, config conf, arena scratch) +{ + u8buf *out = conf.out; + escape *esc = scratch.esc; + + u32 peoff = readu32(dll, 0x3c, esc); + s8 pe = slice2(dll, peoff, esc); + s8 pehdr = slice3(pe, 0, 4, esc); + if (!equals(s8("PE\0\0"), pehdr)) { + throw(esc, s8("not a PE file")); + } + + u16 nsections = readu16(pe, 4+ 2, esc); + u16 hdrsize = readu16(pe, 4+16, esc); + + enum { PE32, PE64 }; + u16 magic = readu16(pe, 4+20, esc); + i32 type = -1; + switch (magic) { + default: throw(esc, s8("unknown PE magic")); + case 0x010b: type = PE32; break; + case 0x020b: type = PE64; break; + } + + vm map = {0}; + i32 loadlen = nsections>96 ? 96 : nsections; + map.regions = new(&scratch, loadlen, region); + s8 sections = slice2(pe, 4+20+hdrsize, esc); + for (i32 i = 0; i < loadlen; i++) { + u32 vsize = readu32(sections, 40*i+ 8, esc); + u32 vaddr = readu32(sections, 40*i+12, esc); + u32 rsize = readu32(sections, 40*i+16, esc); + u32 raddr = readu32(sections, 40*i+20, esc); + + i32 r = map.len++; + map.regions[r].beg = vaddr; + map.regions[r].end = checkadd(vaddr, vsize, esc); + if (r) { + region prev = map.regions[r-1]; + if (prev.beg>=vaddr || prev.end>vaddr) { + throw(esc, s8("invalid section order")); + } + } + + u32 rend = checkadd(raddr, rsize, esc); + map.regions[r].mem = slice3(dll, raddr, rend, esc); + if (vsize > rsize) { + // Padded sections (e.g. .bss) unlikely interesting: discard + map.len--; + } else if (vsize < rsize) { + // Truncated sections *are* usually interesting. Go figure. + map.regions[r].mem.len = vsize; + } + } + + u32 edataoff = readu32(pe, 24+(type==PE32 ? 96 : 112), esc); + u32 edatalen = readu32(pe, 24+(type==PE32 ? 100 : 116), esc); + u32 edataend = checkadd(edataoff, edatalen, esc); + s8 edata = loadrva(map, edataoff, esc); + + if (conf.exports && edatalen) { + print(out, s8("EXPORTS\n")); + + u32 ordbase = readu32(edata, 4*4, esc); + i32 naddrs = readu32(edata, 5*4, esc); + i32 nnames = readu32(edata, 6*4, esc); + if (naddrs<0 || nnames<0) { + throw(esc, s8("invalid export count")); + } + + // If naddrs is huge, this will fail here with OOM + u8 *seen = new(&scratch, naddrs, u8); + + u32 addrsoff = readu32(edata, 7*4, esc); + s8 addrs = loadrva(map, addrsoff, esc); + u32 namesoff = readu32(edata, 8*4, esc); + s8 names = loadrva(map, namesoff, esc); + u32 ordsoff = readu32(edata, 9*4, esc); + s8 ordinals = loadrva(map, ordsoff, esc); + + // If nnames is huge, the loop will EOF before overflow + for (i32 i = 0; i < nnames; i++) { + u16 ordinal = readu16(ordinals, i*2, esc); + if (ordinal >= naddrs) { + throw(esc, s8("invalid export ordinal")); + } + seen[ordinal] = 1; + + // If RVA points in .edata it's a forwarder name + s8 module = {0}; + u32 addr = readu32(addrs, ordinal*4, esc); + if (addr>=edataoff && addr")); + } + print(out, s8("\n")); + } + + for (i32 i = 0; i < naddrs; i++) { + if (!seen[i]) { + print(out, s8("\t")); + printu32(out, checkadd(i, ordbase, esc)); + print(out, s8("\t\n")); + } + } + } + + u32 idataoff = readu32(pe, 24+(type==PE32 ? 104 : 120), esc); + u32 idatalen = readu32(pe, 24+(type==PE32 ? 108 : 124), esc); + s8 idata = loadrva(map, idataoff, esc); + + if (conf.imports && idatalen) { + // The PE specification says the last import directory table + // entry is all zeros, indicating the directory end. However, + // MSVC link.exe is buggy and does not reliably produce this + // null entry. Instead the directory runs into import lookup + // tables and string table, causing the directory to read as + // garbage. We have two workarounds: + // + // 1. Track the earliest import lookup table RVA, and stop + // reading if the directory would overlap it. + // 2. Don't treat garbage RVA fields as errors, just stop + // reading the table (Binutils strategy). + // + // This issue was crashing objdump back in 2005. See Binutils + // commit a50b216054a4. + u32 firsttable = -1; + + for (i32 i = 0;; i++) { + if (idataoff + i*20 == firsttable) { + // Probably the link.exe bug. We're now overlapping an + // import lookup table, so stop reading the import + // directory. The left-side sum might overflow, but + // that's fine. This is just a heuristic. + break; + } + + u32 tableoff = readu32(idata, i*20+ 0, esc); + u32 nameoff = readu32(idata, i*20+12, esc); + if (!tableoff || !nameoff) break; + + s8 name = nullterm(loadrva(map, nameoff, esc)); + if (!name.data) break; // ignore link.exe bug + + printname(out, name); + print(out, s8("\n")); + + s8 table = loadrva(map, tableoff, esc); + if (!table.data) break; // ignore link.exe bug + firsttable = firsttable>31) { + printu32(out, addr&0x7fffffff); + print(out, s8("\t")); + } else { + s8 entry = loadrva(map, addr, esc); + u16 hint = readu16(entry, 0, esc); + printu32(out, hint); + print(out, s8("\t")); + s8 name = nullterm(slice2(entry, 2, esc)); + printname(out, name); + } + print(out, s8("\n")); + } + } + } +} + +static void processpath(s8 path, config conf, arena scratch) +{ + s8 dll = osload(&scratch, path); + if (!dll.data) { + throw(scratch.esc, s8("could not load file")); + } + processpe(dll, conf, scratch); +} + +static b32 peports(i32 argc, s8 *argv, arena scratch) +{ + b32 ok = 1; + u8buf out[1] = {newu8buf(&scratch, 1)}; + u8buf err[1] = {newu8buf(&scratch, 2)}; + + config conf = {0}; + conf.out = out; + conf.err = err; + switch (parseopts(&conf, argc, argv)) { + case OPT_OK: break; + case OPT_EXIT: return 1; + case OPT_ERR: return 0; + } + + if (conf.optind == argc) { + static s8 fakeargv[] = {s8("peports"), s8("-")}; + argc = countof(fakeargv); + argv = fakeargv; + conf.optind = 1; + } + + escape esc = {0}; + scratch.esc = &esc; + for (i32 i = conf.optind; i < argc; i++) { + if (catch(&esc)) { + flush(out); + print(err, s8("peports: ")); + print(err, esc.err); + print(err, s8(": ")); + print(err, argv[i]); // NOTE: UTF-8 + print(err, s8("\n")); + flush(err); + ok &= 1; + continue; + } + processpath(argv[i], conf, scratch); + } + + flush(out); + ok &= !out->err; + return ok; +} + + +#if _WIN32 +// $ gcc -nostartfiles -o peports.exe peports.c +// $ clang-cl peports.c /link /subsystem:console +// kernel32.lib shell32.lib libvcruntime.lib + +#define W32(r) __declspec(dllimport) r __stdcall +W32(b32) CloseHandle(uz); +W32(c16 **) CommandLineToArgvW(c16 *, i32 *); +W32(b32) CreateFileW(c16 *, i32, i32, uz, i32, i32, uz); +W32(void) ExitProcess(i32) __attribute((noreturn)); +W32(c16 *) GetCommandLineW(void); +W32(uz) GetStdHandle(i32); +W32(i32) MultiByteToWideChar(i32, i32, u8 *, i32, c16 *, i32); +W32(b32) ReadFile(uz, u8 *, i32, i32 *, uz); +W32(i32) WideCharToMultiByte(i32, i32, c16 *, i32, u8 *, i32, uz, uz); +W32(b32) WriteFile(uz, u8 *, i32, i32 *, uz); + +static i32 truncsize(iz len, i32 max) +{ + return maxbeg; + iz avail = a->end - a->beg; + avail -= avail / 8; // don't fill the arena to the brim + for (;;) { + i32 max = 1<<21; + i32 len; + ReadFile(handle, r.data+r.len, truncsize(avail-r.len, max), &len, 0); + if (len < 1) break; + r.len += len; + } + a->beg += r.len; + + if (close) CloseHandle(handle); + return r; +} + +static b32 oswrite(i32 fd, u8 *buf, i32 len) +{ + uz h = GetStdHandle(-10 - fd); + return WriteFile(h, buf, len, &len, 0); +} + +__attribute((force_align_arg_pointer)) +void mainCRTStartup(void) +{ + static byte mem[sizeof(uz)<<26]; // 256/512 MiB + arena scratch = {0}; + scratch.beg = mem; + asm ("" : "+r"(scratch.beg)); // launder the pointer + scratch.end = scratch.beg + countof(mem); + + c16 *cmd = GetCommandLineW(); + i32 argc = 0; + c16 **argvw = CommandLineToArgvW(cmd, &argc); + s8 *argv = new(&scratch, argc, s8); + for (i32 i = 0; i < argc; i++) { + i32 len = WideCharToMultiByte(65001, 0, argvw[i], -1, 0, 0, 0, 0); + argv[i].data = new(&scratch, len, u8); + argv[i].len = len ? len-1 : len; + WideCharToMultiByte(65001, 0, argvw[i], -1, argv[i].data, len, 0, 0); + } + + b32 ok = peports(argc, argv, scratch); + ExitProcess(!ok); +} + + +#elif __AFL_COMPILER +// $ afl-gcc-fast -g3 -fsanitize=undefined peports.c +// $ mkdir i +// $ cp corpus.dll i/ +// $ afl-fuzz -ii -oo ./a.out +#include +#include + +__AFL_FUZZ_INIT(); + +static b32 oswrite(i32, u8 *, i32) { return 1; } +static s8 osload(arena *, s8) { __builtin_trap(); } + +int main(void) +{ + __AFL_INIT(); + + iz cap = 1<<20; + arena a = {0}; + a.beg = malloc(cap); + a.end = a.beg + cap; + + config c = {0}; + c.exports = 1; + c.imports = 1; + c.out = new(&a, 1, u8buf); + *c.out = newu8buf(&a, 1); + c.err = new(&a, 1, u8buf); + *c.err = newu8buf(&a, 2); + + s8 dll = {0}; + dll.data = __AFL_FUZZ_TESTCASE_BUF; + while (__AFL_LOOP(10000)) { + dll.len = __AFL_FUZZ_TESTCASE_LEN; + a.esc = &(escape){0}; + if (!catch(a.esc)) { + processpe(dll, c, a); + } + } +} + + +#else // POSIX-ish? +// $ cc -o peports peports.c +#include +#include +#include + +static s8 osload(arena *a, s8 path) +{ + s8 r = {0}; + int fd = 0; + b32 closeit = 1; + + if (equals(path, s8("-"))) { + closeit = 0; + } else { + // NOTE: Assume the path is null-terminated because it came + // straight from argv. This program does not construct paths. + char *cpath = (char *)path.data; + fd = open(cpath, O_RDONLY); + if (fd == -1) return r; + } + + r.data = (u8 *)a->beg; + iz avail = a->end - a->beg; + avail -= avail / 8; // don't fill the arena to the brim + while (r.len < avail) { + iz len = read(fd, r.data+r.len, avail-r.len); + if (len < 1) break; + r.len += len; + } + a->beg += r.len; + + if (closeit) close(fd); + return r; +} + +static b32 oswrite(i32 fd, u8 *buf, i32 len) +{ + for (i32 off = 0; off < len;) { + i32 r = (i32)write(fd, buf+off, len-off); + if (r < 1) return 0; + off += r; + } + return 1; +} + +int main(int argc, char **argv) +{ + static byte mem[sizeof(uz)<<26]; // 256/512 MiB + arena scratch = {0}; + scratch.beg = mem; + asm ("" : "+r"(scratch.beg)); // launder the pointer + scratch.end = scratch.beg + countof(mem); + + s8 *args = new(&scratch, argc, s8); + for (int i = 0; i < argc; i++) { + args[i].data = (u8 *)argv[i]; + args[i].len = strlen(argv[i]); + } + b32 ok = peports(argc, args, scratch); + return !ok; +} +#endif diff --git a/.gnu-windows/src/pkg-config.c b/.gnu-windows/src/pkg-config.c index e3036af428..1f7f356e75 100644 --- a/.gnu-windows/src/pkg-config.c +++ b/.gnu-windows/src/pkg-config.c @@ -1,73 +1,46 @@ // u-config: a small, simple, portable pkg-config clone // https://github.com/skeeto/u-config // $ cc -nostartfiles -o pkg-config.exe pkg-config.c -// $ cl pkg-config.c // This is free and unencumbered software released into the public domain. +#define VERSION "0.33.1" -// Fundamental definitions +typedef unsigned char u8; +typedef signed int b32; +typedef signed int i32; +typedef unsigned int u32; +typedef __PTRDIFF_TYPE__ size; +typedef char byte; -#define VERSION "0.31.1" - -typedef int Size; -#define Size_MASK ((unsigned)-1) -#define Size_MAX ((Size)(Size_MASK >> 1)) - -#define SIZEOF(x) (Size)(sizeof(x)) -#define COUNTOF(a) (SIZEOF(a)/SIZEOF(a[0])) - -typedef int Bool; -typedef unsigned char Byte; - -#if __GNUC__ - #define TRAP __builtin_trap() - #define NORETURN __attribute__((noreturn)) -#elif _MSC_VER - #define TRAP __debugbreak() - #define NORETURN __declspec(noreturn) -#else - #define TRAP *(volatile int *)0 = 0 - #define NORETURN -#endif - -#ifdef DEBUG - #define ASSERT(c) if (!(c)) TRAP -#else - #define ASSERT(c) -#endif +#define assert(c) while (!(c)) __builtin_unreachable() +#define countof(a) (size)(sizeof(a) / sizeof(*(a))) +#define new(a, t, n) (t *)alloc(a, sizeof(t), n) +#define s8(s) {(u8 *)s, countof(s)-1} +#define S(s) (s8)s8(s) typedef struct { - Byte *s; - Size len; -} Str; - -#ifdef __cplusplus - #define S(s) makestr((Byte *)s, SIZEOF(s)-1) - static inline Str makestr(Byte *s, Size len) - { - Str r = {s, len}; - return r; - } -#else - #define S(s) (Str){(Byte *)s, SIZEOF(s)-1} -#endif + u8 *s; + size len; +} s8; typedef struct { - Str mem; - Size off; -} Arena; + byte *beg; + byte *end; +} arena; typedef struct { - Arena arena; - Str *args; - Size nargs; - Str envpath; // $PKG_CONFIG_PATH or empty - Str fixedpath; // $PKG_CONFIG_LIBDIR or default - Str top_builddir; // $PKG_CONFIG_TOP_BUILD_DIR or default - Str sys_incpath; // $PKG_CONFIG_SYSTEM_INCLUDE_PATH or default - Str sys_libpath; // $PKG_CONFIG_SYSTEM_LIBRARY_PATH or default - Bool define_prefix; - Byte delim; -} Config; + arena perm; + s8 *args; + size nargs; + s8 envpath; // $PKG_CONFIG_PATH or empty + s8 fixedpath; // $PKG_CONFIG_LIBDIR or default + s8 top_builddir; // $PKG_CONFIG_TOP_BUILD_DIR or default + s8 sys_incpath; // $PKG_CONFIG_SYSTEM_INCLUDE_PATH or default + s8 sys_libpath; // $PKG_CONFIG_SYSTEM_LIBRARY_PATH or default + s8 print_sysinc; // $PKG_CONFIG_ALLOW_SYSTEM_CFLAGS or empty + s8 print_syslib; // $PKG_CONFIG_ALLOW_SYSTEM_LIBS or empty + b32 define_prefix; + u8 delim; +} config; // Platform API @@ -75,510 +48,485 @@ typedef struct { // Application entry point. Returning from this function indicates the // application itself completed successfully. However, an os_write error // may result in a non-zero exit. -static void appmain(Config); +static void uconfig(config *); -typedef enum {MapFile_OK, MapFile_NOTFOUND, MapFile_READERR} MapFileStatus; +enum { filemap_OK, filemap_NOTFOUND, filemap_READERR }; typedef struct { - Str contents; - MapFileStatus status; -} MapFileResult; + s8 data; + i32 status; +} filemap; // Load a file into memory, maybe using the arena. The path must include // a null terminator since it may be passed directly to the OS interface. -static MapFileResult os_mapfile(Arena *, Str path); +static filemap os_mapfile(arena *, s8 path); // Write buffer to stdout (1) or stderr (2). The platform must detect // write errors and arrange for an eventual non-zero exit status. -static void os_write(int fd, Str); +static void os_write(i32 fd, s8); // Immediately exit the program with a non-zero status. -NORETURN static void os_fail(void); +static void os_fail(void) __attribute((noreturn)); // Application -NORETURN static void oom(void) +static void oom(void) { os_write(2, S("pkg-config: out of memory\n")); os_fail(); } -static Bool digit(Byte c) +static b32 digit(u8 c) { return c>='0' && c<='9'; } -static Bool whitespace(Byte c) +static b32 whitespace(u8 c) { switch (c) { - case '\t': case '\n': case '\b': case '\f': case '\r': case ' ': + case '\t': case '\n': case '\r': case ' ': return 1; } return 0; } -static Bool pathsep(Byte c) +static byte *fillbytes(byte *dst, byte c, size len) { - return c=='/' || c=='\\'; -} - -static Str fillstr(Str s, Byte b) -{ - for (Size i = 0; i < s.len; i++) { - s.s[i] = b; + byte *r = dst; + for (; len; len--) { + *dst++ = c; } - return s; + return r; } -static void *alloc(Arena *a, Size size) +static void u8copy(u8 *dst, u8 *src, size n) { - ASSERT(size >= 0); - Size align = -size & (SIZEOF(void *) - 1); - Size avail = a->mem.len - a->off; - if (avail-align < size) { - oom(); + assert(n >= 0); + for (; n; n--) { + *dst++ = *src++; } - Byte *p = a->mem.s + a->off; - a->off += size + align; - return p; } -static void *allocarray(Arena *a, Size size, Size count) +static i32 u8compare(u8 *a, u8 *b, size n) { - ASSERT(size > 0); - ASSERT(count >= 0); - if (count > Size_MAX/size) { - oom(); + for (; n; n--) { + i32 d = *a++ - *b++; + if (d) return d; } - return alloc(a, size*count); -} - -static Str newstr(Arena *a, Size len) -{ - Str r = {(Byte *)alloc(a, len), len}; - return r; + return 0; } -static void *zalloc(Arena *a, Size size) +static b32 pathsep(u8 c) { - Str r = newstr(a, size); - return fillstr(r, 0).s; + return c=='/' || c=='\\'; } -static Str maxstr(Arena *a) +__attribute((malloc, alloc_size(3, 2))) +static byte *alloc(arena *a, size objsize, size count) { - Size len = a->mem.len - a->off; - return newstr(a, len); + assert(objsize > 0); + assert(count >= 0); + size alignment = -((u32)objsize * (u32)count) & 7; + size available = a->end - a->beg - alignment; + if (count > available/objsize) { + oom(); + } + size total = objsize * count; + return fillbytes(a->end -= total + alignment, 0, total); } -// Fill free space with garbage when debugging. -static void shredfree(Arena *a) +static s8 news8(arena *perm, size len) { - (void)a; - #ifdef DEBUG - Arena temp = *a; - fillstr(maxstr(&temp), 0xa5); - #endif + s8 r = {0}; + r.s = new(perm, u8, len); + r.len = len; + return r; } -static Str fromptrs(Byte *beg, Byte *end) +static s8 s8span(u8 *beg, u8 *end) { - ASSERT(beg); - ASSERT(end); - ASSERT(end >= beg); - Str s = {beg, (Size)(end - beg)}; + assert(beg); + assert(end); + assert(end >= beg); + s8 s = {0}; + s.s = beg; + s.len = end - beg; return s; } // Copy src into dst returning the remaining portion of dst. -static Str copy(Str dst, Str src) -{ - ASSERT(dst.len >= src.len); - for (Size i = 0; i < src.len; i++) { - dst.s[i] = src.s[i]; - } - Str r = {dst.s+src.len, dst.len-src.len}; - return r; -} - -// Compare strings, returning -1, 0, or +1. -static int orderstr(Str a, Str b) +static s8 s8copy(s8 dst, s8 src) { - // NOTE: "null" strings are still valid strings - Size len = a.len= src.len); + u8copy(dst.s, src.s, src.len); + dst.s += src.len; + dst.len -= src.len; + return dst; } -static Bool equals(Str a, Str b) +static b32 s8equals(s8 a, s8 b) { - return 0 == orderstr(a, b); + return a.len==b.len && !u8compare(a.s, b.s, a.len); } -static Str cuthead(Str s, Size off) +static s8 cuthead(s8 s, size off) { - ASSERT(off >= 0); - ASSERT(off <= s.len); + assert(off >= 0); + assert(off <= s.len); s.s += off; s.len -= off; return s; } -static Str takehead(Str s, Size len) +static s8 takehead(s8 s, size len) { - ASSERT(len >= 0); - ASSERT(len <= s.len); + assert(len >= 0); + assert(len <= s.len); s.len = len; return s; } -static Str cuttail(Str s, Size len) +static s8 cuttail(s8 s, size len) { - ASSERT(len >= 0); - ASSERT(len <= s.len); - Str r = {s.s, s.len-len}; - return r; + assert(len >= 0); + assert(len <= s.len); + s.len -= len; + return s; } -static Str taketail(Str s, Size len) +static s8 taketail(s8 s, size len) { return cuthead(s, s.len-len); } -static Bool startswith(Str s, Str prefix) +static b32 startswith(s8 s, s8 prefix) { - return s.len>=prefix.len && equals(takehead(s, prefix.len), prefix); + return s.len>=prefix.len && s8equals(takehead(s, prefix.len), prefix); } -static Size hash(Str s) +static u32 s8hash(s8 s) { - unsigned long long h = 257; - for (Size i = 0; i < s.len; i++) { + u32 h = 0x811c9dc5; + for (size i = 0; i < s.len; i++) { h ^= s.s[i]; - h *= 1111111111111111111; + h *= 0x01000193; } - h ^= h >> 33; - return (Size)(h & Size_MASK); + return h; } typedef struct { - Str head; - Str tail; -} StrPair; + s8 head; + s8 tail; +} s8pair; -static StrPair digits(Str s) +static s8pair digits(s8 s) { - Size i = 0; - for (; ikey)) { - case -1: target = parent->child + 0; break; - case 0: return parent; - case +1: target = parent->child + 1; break; - } +// Encode paths with illegal UTF-8 bytes. Reversible. Allows white space +// and meta characters in paths to behave differently. Encode paths +// before variable assignment. Reversed by dequote() when printed. +static u8 pathencode(u8 c) +{ + // NOTE: space classification must agree with whitespace() + switch (c) { + case '\t': return 0xf8; + case '\n': return 0xf9; + case '\r': return 0xfa; + case ' ' : return 0xfb; + case '$' : return 0xfc; + case '(' : return 0xfd; + case ')' : return 0xfe; } + return c; +} - // None found, insert a new leaf - if (!a) { - return 0; // "only browsing, thanks" +static u8 pathdecode(u8 c) +{ + switch (c) { + case 0xf8: return '\t'; + case 0xf9: return '\n'; + case 0xfa: return '\r'; + case 0xfb: return ' ' ; + case 0xfc: return '$' ; + case 0xfd: return '(' ; + case 0xfe: return ')' ; } - Treap *node = (Treap *)zalloc(a, size); - node->key = key; - node->parent = parent; - *target = node; - - // Randomly rotate the tree according to the hash - Size keyhash = hash(key); - while (node->parent && hash(node->parent->key)parent; + return c; +} - // Swap places with parent, also updating grandparent - node->parent = parent->parent; - parent->parent = node; - if (node->parent) { - int i = node->parent->child[0] == parent; - node->parent->child[!i] = node; - } else { - *t = node; - } +static s8 s8pathencode(s8 s, arena *perm) +{ + b32 encode = 0; + for (size i = 0; ichild[0] == node; - parent->child[!i] = node->child[i]; - if (node->child[i]) { - node->child[i]->parent = parent; - } - node->child[i] = parent; + s8 r = news8(perm, s.len); + for (size i = 0; i < s.len; i++) { + r.s[i] = pathencode(s.s[i]); } - return node; + return r; } typedef struct { - Str buf; - Str avail; - Arena *a; - int fd; -} Out; + u8 *buf; + size cap; + size len; + arena *perm; + i32 fd; +} u8buf; // Buffered output for os_write(). -static Out newoutput(Arena *a, int fd, Size len) +static u8buf *newfdbuf(arena *perm, i32 fd, size cap) { - Str buf = newstr(a, len); - Out out = {buf, buf, 0, fd}; - return out; + u8buf *b = new(perm, u8buf, 1); + b->cap = cap; + b->buf = new(perm, u8, cap); + b->fd = fd; + return b; } -static Out newnullout(void) +static u8buf *newnullout(arena *perm) { - Out out = {0}; - out.fd = -1; - return out; + u8buf *b = new(perm, u8buf, 1); + b->fd = -1; + return b; } // Output to a dynamically-grown arena buffer. The arena cannot be used // again until this buffer is finalized. -static Out newmembuf(Arena *a) +static u8buf newmembuf(arena *perm) { - Str max = maxstr(a); - Out out = {max, max, a, 0}; - return out; + u8buf b = {0}; + b.buf = (u8 *)perm->beg; + b.cap = perm->end - perm->beg; + b.perm = perm; + return b; +} + +static s8 gets8(u8buf *b) +{ + s8 s = {0}; + s.s = b->buf; + s.len = b->len; + return s; } // Close the stream and release the arena, returning the result buffer. -static Str finalize(Out *out) +static s8 finalize(u8buf *b) { - ASSERT(!out->fd); - Size len = out->buf.len - out->avail.len; - out->a->off -= out->buf.len; - return newstr(out->a, len); + assert(!b->fd); + b->perm->beg += b->len; + return gets8(b); } -static void flush(Out *out) +static void flush(u8buf *b) { - ASSERT(out->fd); - if (out->buf.len != out->avail.len) { - Str fill = {out->buf.s, out->buf.len-out->avail.len}; - os_write(out->fd, fill); - out->avail = out->buf; + switch (b->fd) { + case -1: break; // /dev/null + case 0: oom(); + break; + default: if (b->len) { + os_write(b->fd, gets8(b)); + } } + b->len = 0; } -static void outstr(Out *out, Str s) +static void prints8(u8buf *b, s8 s) { - if (out->fd == -1) { + if (b->fd == -1) { return; // /dev/null } - - if (out->fd == 0) { - // Output to a memory buffer, not a stream - if (out->avail.len < s.len) { - oom(); - } - out->avail = copy(out->avail, s); - return; - } - - // Copy into the stream buffer - while (s.len) { - if (out->avail.len >= s.len) { - out->avail = copy(out->avail, s); - s.len = 0; - } else if (out->buf.len==out->avail.len && s.len>=out->buf.len) { - os_write(out->fd, s); - s.len = 0; - } else { - Size len = out->avail.len; - Str head = takehead(s, len); - s = cuthead(s, len); - out->avail = copy(out->avail, head); - flush(out); + for (size off = 0; off < s.len;) { + size avail = b->cap - b->len; + size count = availbuf+b->len, s.s+off, count); + b->len += count; + off += count; + if (b->len == b->cap) { + flush(b); } } } -static void outbyte(Out *out, Byte b) +static void printu8(u8buf *b, u8 c) { - Str s = {&b, 1}; - outstr(out, s); + prints8(b, s8span(&c, &c+1)); } -typedef struct Var { - Treap node; - Str value; -} Var; +typedef struct env env; +struct env { + env *child[4]; + s8 name; + s8 value; +}; -typedef struct { - Treap *vars; -} Env; - -// Return a pointer to the binding so that the caller can choose to fill -// it. The arena is optional. If given, the binding will be created and -// set to a null string. An unallocated, zero-initialized environment is -// a valid empty environment. -static Str *insert(Arena *a, Env *e, Str name) +// Return a pointer to the binding so that the caller can bind it. The +// arena is optional. If given, the binding will be created and set to a +// null string. A null pointer is a valid empty environment. +static s8 *insert(env **e, s8 name, arena *perm) { - Var *var = (Var *)treapinsert(a, &e->vars, name, SIZEOF(*var)); - return var ? &var->value : 0; + for (u32 h = s8hash(name); *e; h <<= 2) { + if (s8equals((*e)->name, name)) { + return &(*e)->value; + } + e = &(*e)->child[h>>30]; + } + if (!perm) { + return 0; + } + *e = new(perm, env, 1); + (*e)->name = name; + return &(*e)->value; } // Try to find the binding in the global environment, then failing that, // the second environment. Returns a null string if no entry was found. -// An unallocated, zero-initialized environment is valid for lookups. -static Str lookup(Env *global, Env *env, Str name) +// A null pointer is valid for lookups. +static s8 lookup(env *global, env *env, s8 name) { - Str *s = insert(0, global, name); - if (s) { - return *s; - } - s = insert(0, env, name); - if (s) { - return *s; - } - Str r = {0}; - return r; + s8 *s = 0; + s8 null = {0}; + s = s ? s : insert(&global, name, 0); + s = s ? s : insert(&env, name, 0); + s = s ? s : &null; + return *s; } -static Str dirname(Str path) +static s8 dirname(s8 path) { - Size len = path.len; + size len = path.len; while (len>0 && !pathsep(path.s[--len])) {} return takehead(path, len); } -static Str basename(Str path) +static s8 basename(s8 path) { - Size len = path.len; + size len = path.len; for (; len>0 && !pathsep(path.s[len-1]); len--) {} return taketail(path, path.len-len); } -static Str buildpath(Arena *a, Str dir, Str pc) +static s8 buildpath(s8 dir, s8 pc, arena *perm) { - Str sep = S("/"); - Str suffix = S(".pc\0"); - Size pathlen = dir.len + sep.len + pc.len + suffix.len; - Str path = newstr(a, pathlen); - Str p = path; - p = copy(p, dir); - p = copy(p, sep); - p = copy(p, pc); - copy(p, suffix); + s8 sep = S("/"); + s8 suffix = S(".pc\0"); + size pathlen = dir.len + sep.len + pc.len + suffix.len; + s8 path = news8(perm, pathlen); + s8 p = path; + p = s8copy(p, dir); + p = s8copy(p, sep); + p = s8copy(p, pc); + s8copy(p, suffix); return path; } -typedef enum {Pkg_DIRECT=1<<0, Pkg_PUBLIC=1<<1} PkgFlags; +enum { pkg_DIRECT=1<<0, pkg_PUBLIC=1<<1 }; -typedef struct Pkg { - Treap node; - struct Pkg *list; // total load order list - Str path; - Str realname; - Str contents; - Env env; - int flags; +typedef struct pkg pkg; +struct pkg { + pkg *child[4]; + pkg *list; // total load order list + s8 path; + s8 realname; + s8 contents; + env *env; + i32 flags; #define PKG_NFIELDS 10 - Str name; - Str description; - Str url; - Str version; - Str requires; - Str requiresprivate; - Str conflicts; - Str libs; - Str libsprivate; - Str cflags; -} Pkg; - -static Str *fieldbyid(Pkg *p, int id) -{ - ASSERT(id >= 0); - ASSERT(id < PKG_NFIELDS); + s8 name; + s8 description; + s8 url; + s8 version; + s8 requires; + s8 requiresprivate; + s8 conflicts; + s8 libs; + s8 libsprivate; + s8 cflags; +}; + +static s8 *fieldbyid(pkg *p, i32 id) +{ + assert(id >= 0); + assert(id < PKG_NFIELDS); return &p->name + id; } -static Str *fieldbyname(Pkg *p, Str name) -{ - static const unsigned char offs[] = {0,4,15,18,25,25,41,50,50,62}; - static const unsigned char lens[] = {4,11,3,7,8,16,9,4,12,6}; - static const Byte fields[] = - "Name" "Description" "URL" "Version" "Requires.private" - "Conflicts" "Libs.private" "Cflags"; - for (int i = 0; i < COUNTOF(offs); i++) { - Str field = {(Byte *)fields+offs[i], lens[i]}; - if (equals(field, name)) { +static s8 *fieldbyname(pkg *p, s8 name) +{ + static const s8 fields[] = { + s8("Name"), + s8("Description"), + s8("URL"), + s8("Version"), + s8("Requires"), + s8("Requires.private"), + s8("Conflicts"), + s8("Libs"), + s8("Libs.private"), + s8("Cflags") + }; + for (i32 i = 0; i < countof(fields); i++) { + if (s8equals(fields[i], name)) { return fieldbyid(p, i); } } @@ -586,43 +534,49 @@ static Str *fieldbyname(Pkg *p, Str name) } typedef struct { - Treap *pkgs; - Pkg *head, *tail; - Size count; -} Pkgs; + pkg *pkgs; + pkg *head; + pkg **tail; + size count; +} pkgs; + +static pkgs *newpkgs(arena *perm) +{ + pkgs *p = new(perm, pkgs, 1); + p->tail = &p->head; + return p; +} // Locate a previously-loaded package, or allocate zero-initialized // space in the set for a new package. -static Pkg *locate(Arena *a, Pkgs *t, Str realname) -{ - Pkg *p = (Pkg *)treapinsert(a, &t->pkgs, realname, SIZEOF(*p)); - if (!p->realname.s) { - t->count++; - p->realname = realname; - if (!t->head) { - t->head = t->tail = p; - } else { - t->tail->list = p; - t->tail = p; +static pkg *locate(pkgs *t, s8 realname, arena *perm) +{ + pkg **p = &t->pkgs; + for (u32 h = s8hash(realname); *p; h <<= 2) { + if (s8equals((*p)->realname, realname)) { + return *p; } + p = &(*p)->child[h>>30]; } - return p; + + *p = new(perm, pkg, 1); + (*p)->realname = realname; + t->count++; + *t->tail = *p; + t->tail = &(*p)->list; + return *p; } -typedef enum { - Parse_OK, - Parse_DUPFIELD, - Parse_DUPVARABLE -} ParseStatus; +enum { parse_OK, parse_DUPFIELD, parse_DUPVARABLE }; typedef struct { - Pkg pkg; - Str dupname; - ParseStatus status; -} ParseResult; + pkg pkg; + s8 dupname; + i32 err; +} parseresult; // Return the number of escape bytes at the beginning of the input. -static Size escaped(Str s) +static size escaped(s8 s) { if (startswith(s, S("\\\n"))) { return 2; @@ -634,14 +588,14 @@ static Size escaped(Str s) } // Return a copy of the input with the escapes squashed out. -static Str stripescapes(Arena *a, Str s) +static s8 stripescapes(arena *perm, s8 s) { - Size len = 0; - Str c = newstr(a, s.len); - for (Size i = 0; i < s.len; i++) { - Byte b = s.s[i]; + size len = 0; + s8 c = news8(perm, s.len); + for (size i = 0; i < s.len; i++) { + u8 b = s.s[i]; if (b == '\\') { - Size r = escaped(cuthead(s, i)); + size r = escaped(cuthead(s, i)); if (r) { i += r - 1; } else if (is) { - ParseResult dup = {0}; + parseresult dup = {0}; dup.dupname = name; - dup.status = Parse_DUPFIELD; + dup.err = parse_DUPFIELD; return dup; } break; @@ -712,7 +666,7 @@ static ParseResult parsepackage(Arena *a, Str src) // Skip leading space; newlines may be escaped with a backslash while (p < e) { if (*p == '\\') { - Size r = escaped(fromptrs(p, e)); + size r = escaped(s8span(p, e)); if (r) { p += r; } else { @@ -725,7 +679,7 @@ static ParseResult parsepackage(Arena *a, Str src) } } - Bool cleanup = 0; + b32 cleanup = 0; end = beg = p; for (; pindex == p->nargs) { - OptionResult r = {0}; return r; } for (;;) { - Str arg = p->args[p->index++]; + s8 arg = p->args[p->index++]; if (p->dashdash || arg.len<2 || arg.s[0]!='-') { - OptionResult r = {0}; r.arg = arg; r.ok = 1; return r; } - if (!p->dashdash && equals(arg, S("--"))) { + if (!p->dashdash && s8equals(arg, S("--"))) { p->dashdash = 1; continue; } - OptionResult r = {0}; r.isoption = 1; r.ok = 1; arg = cuthead(arg, 1); - Cut c = cut(arg, '='); + cut c = s8cut(arg, '='); if (c.ok) { r.arg = c.head; r.value = c.tail; @@ -829,7 +784,7 @@ static OptionResult nextoption(OptionParser *p) } } -static Str getargopt(Out *err, OptionParser *p, Str option) +static s8 getargopt(u8buf *err, options *p, s8 option) { if (p->index == p->nargs) { missing(err, option); @@ -837,9 +792,9 @@ static Str getargopt(Out *err, OptionParser *p, Str option) return p->args[p->index++]; } -static void usage(Out *out) +static void usage(u8buf *b) { - static const char usage[] = + static const u8 usage[] = "u-config " VERSION " https://github.com/skeeto/u-config\n" "free and unencumbered software released into the public domain\n" "usage: pkg-config [OPTIONS...] [PACKAGES...]\n" @@ -863,97 +818,96 @@ static void usage(Out *out) " PKG_CONFIG_LIBDIR\n" " PKG_CONFIG_TOP_BUILD_DIR\n" " PKG_CONFIG_SYSTEM_INCLUDE_PATH\n" - " PKG_CONFIG_SYSTEM_LIBRARY_PATH\n"; - outstr(out, S(usage)); + " PKG_CONFIG_SYSTEM_LIBRARY_PATH\n" + " PKG_CONFIG_ALLOW_SYSTEM_CFLAGS\n" + " PKG_CONFIG_ALLOW_SYSTEM_LIBS\n"; + prints8(b, S(usage)); } -typedef struct StrListNode { - struct StrListNode *next; - Str entry; -} StrListNode; +typedef struct s8node s8node; +struct s8node { + s8node *next; + s8 str; +}; typedef struct { - StrListNode *head; - StrListNode *tail; -} StrList; - -static void append(Arena *a, StrList *list, Str str) -{ - StrListNode *node = (StrListNode *)alloc(a, SIZEOF(*node)); - node->next = 0; - node->entry = str; - if (list->tail) { - ASSERT(list->head); - list->tail->next = node; - } else { - ASSERT(!list->tail); - list->head = node; + s8node *head; + s8node **tail; +} s8list; + +static void append(s8list *list, s8 str, arena *perm) +{ + if (!list->tail) { + list->tail = &list->head; } - list->tail = node; + s8node *node = new(perm, s8node, 1); + node->str = str; + *list->tail = node; + list->tail = &node->next; } typedef struct { - StrList list; - Byte delim; -} Search; + s8list list; + u8 delim; +} search; -static Search newsearch(Byte delim) +static search newsearch(u8 delim) { - Search r = {0}; + search r = {0}; r.delim = delim; return r; } -static void appendpath(Arena *a, Search *dirs, Str path) +static void appendpath(search *dirs, s8 path, arena *perm) { while (path.len) { - Cut c = cut(path, dirs->delim); - Str dir = c.head; + cut c = s8cut(path, dirs->delim); + s8 dir = c.head; if (dir.len) { - append(a, &dirs->list, dir); + append(&dirs->list, dir, perm); } path = c.tail; } } -static void prependpath(Arena *a, Search *dirs, Str path) +static void prependpath(search *dirs, s8 path, arena *perm) { if (!dirs->list.head) { // Empty, so appending is the same a prepending - appendpath(a, dirs, path); + appendpath(dirs, path, perm); } else { // Append to an empty Search, then transplant in front - Search temp = newsearch(dirs->delim); - appendpath(a, &temp, path); - temp.list.tail->next = dirs->list.head; + search temp = newsearch(dirs->delim); + appendpath(&temp, path, perm); + *temp.list.tail = dirs->list.head; dirs->list.head = temp.list.head; } } -static Bool realnameispath(Str realname) +static b32 realnameispath(s8 realname) { - return realname.len>3 && equals(taketail(realname, 3), S(".pc")); + return realname.len>3 && s8equals(taketail(realname, 3), S(".pc")); } -static Str pathtorealname(Str path) +static s8 pathtorealname(s8 path) { if (!realnameispath(path)) { return path; } - Size baselen = 0; - for (Size i = 0; i < path.len; i++) { + size baselen = 0; + for (size i = 0; i < path.len; i++) { if (pathsep(path.s[i])) { baselen = i + 1; } } - Str name = cuthead(path, baselen); + s8 name = cuthead(path, baselen); return cuttail(name, 3); } -static Str readpackage(Arena *a, Out *err, Str path, Str realname) +static s8 readpackage(u8buf *err, s8 path, s8 realname, arena *perm) { - if (equals(realname, S("pkg-config"))) { + if (s8equals(realname, S("pkg-config"))) { return S( "Name: u-config\n" "Version: " VERSION "\n" @@ -961,72 +915,70 @@ static Str readpackage(Arena *a, Out *err, Str path, Str realname) ); } - Str null = {0}; - MapFileResult m = os_mapfile(a, path); + s8 null = {0}; + filemap m = os_mapfile(perm, path); switch (m.status) { - case MapFile_NOTFOUND: + case filemap_NOTFOUND: return null; - case MapFile_READERR: - outstr(err, S("pkg-config: ")); - outstr(err, S("could not read package '")); - outstr(err, realname); - outstr(err, S("' from '")); - outstr(err, path); - outstr(err, S("'\n")); + case filemap_READERR: + prints8(err, S("pkg-config: ")); + prints8(err, S("could not read package '")); + prints8(err, realname); + prints8(err, S("' from '")); + prints8(err, path); + prints8(err, S("'\n")); flush(err); os_fail(); - case MapFile_OK: - return m.contents; + case filemap_OK: + return m.data; } - ASSERT(0); - return null; + assert(0); } -static void expand(Out *out, Out *err, Env *global, Pkg *p, Str str) +static void expand(u8buf *out, u8buf *err, env *global, pkg *p, s8 str) { - int top = 0; - Str stack[128]; + i32 top = 0; + s8 stack[128]; stack[top] = str; while (top >= 0) { - Str s = stack[top--]; - for (Size i = 0; i < s.len-1; i++) { + s8 s = stack[top--]; + for (size i = 0; i < s.len-1; i++) { if (s.s[i]=='$' && s.s[i+1]=='{') { - if (top >= COUNTOF(stack)-2) { - outstr(err, S("pkg-config: ")); - outstr(err, S("exceeded max recursion depth in '")); - outstr(err, p->path); - outstr(err, S("'\n")); + if (top >= countof(stack)-2) { + prints8(err, S("pkg-config: ")); + prints8(err, S("exceeded max recursion depth in '")); + prints8(err, p->path); + prints8(err, S("'\n")); flush(err); os_fail(); } - Str head = {s.s, i}; - outstr(out, head); + prints8(out, takehead(s, i)); - Size beg = i + 2; - Size end = beg; + size beg = i + 2; + size end = beg; for (; endenv, name); + s8 value = lookup(global, p->env, name); if (!value.s) { - outstr(err, S("pkg-config: ")); - outstr(err, S("undefined variable '")); - outstr(err, name); - outstr(err, S("' in '")); - outstr(err, p->path); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("undefined variable '")); + prints8(err, name); + prints8(err, S("' in '")); + prints8(err, p->path); + prints8(err, S("'\n")); flush(err); os_fail(); } @@ -1035,93 +987,93 @@ static void expand(Out *out, Out *err, Env *global, Pkg *p, Str str) break; } else if (s.s[i]=='$' && s.s[i+1]=='$') { - Str head = {s.s, i+1}; - outstr(out, head); - Str tail = {s.s+i+2, s.len-i-2}; - stack[++top] = tail; + s8 head = takehead(s, i+1); + prints8(out, head); + stack[++top] = cuthead(s, i+2); s.len = 0; break; } } - outstr(out, s); + prints8(out, s); } } // Merge and expand data from "update" into "base". -static void expandmerge(Arena *a, Out *err, Env *g, Pkg *base, Pkg *update) +static void expandmerge(u8buf *err, env *g, pkg *base, pkg *update, arena *perm) { base->path = update->path; base->contents = update->contents; base->env = update->env; base->flags = update->flags; - for (int i = 0; i < PKG_NFIELDS; i++) { - Out mem = newmembuf(a); - Str src = *fieldbyid(update, i); + for (i32 i = 0; i < PKG_NFIELDS; i++) { + u8buf mem = newmembuf(perm); + s8 src = *fieldbyid(update, i); expand(&mem, err, g, update, src); *fieldbyid(base, i) = finalize(&mem); } } -static Pkg findpackage(Arena *a, Search *dirs, Out *err, Str realname) +static pkg findpackage(search *dirs, u8buf *err, s8 realname, arena *perm) { - Str path = {0, 0}; - Str contents = {0, 0}; + s8 path = {0}; + s8 contents = {0}; if (realnameispath(realname)) { - path = newstr(a, realname.len+1); - copy(path, realname).s[0] = 0; - contents = readpackage(a, err, path, realname); + path = news8(perm, realname.len+1); + s8copy(path, realname).s[0] = 0; + contents = readpackage(err, path, realname, perm); path = cuttail(path, 1); // remove null terminator if (contents.s) { realname = pathtorealname(path); } } - for (StrListNode *n = dirs->list.head; n && !contents.s; n = n->next) { - path = buildpath(a, n->entry, realname); - contents = readpackage(a, err, path, realname); + for (s8node *n = dirs->list.head; n && !contents.s; n = n->next) { + path = buildpath(n->str, realname, perm); + contents = readpackage(err, path, realname, perm); path = cuttail(path, 1); // remove null terminator } if (!contents.s) { - outstr(err, S("pkg-config: ")); - outstr(err, S("could not find package '")); - outstr(err, realname); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("could not find package '")); + prints8(err, realname); + prints8(err, S("'\n")); flush(err); os_fail(); } - ParseResult r = parsepackage(a, contents); - switch (r.status) { - case Parse_DUPVARABLE: - outstr(err, S("pkg-config: ")); - outstr(err, S("duplicate variable '")); - outstr(err, r.dupname); - outstr(err, S("' in '")); - outstr(err, path); - outstr(err, S("'\n")); + parseresult r = parsepackage(contents, perm); + switch (r.err) { + case parse_DUPVARABLE: + prints8(err, S("pkg-config: ")); + prints8(err, S("duplicate variable '")); + prints8(err, r.dupname); + prints8(err, S("' in '")); + prints8(err, path); + prints8(err, S("'\n")); flush(err); os_fail(); - case Parse_DUPFIELD: - outstr(err, S("pkg-config: ")); - outstr(err, S("duplicate field '")); - outstr(err, r.dupname); - outstr(err, S("' in '")); - outstr(err, path); - outstr(err, S("'\n")); + case parse_DUPFIELD: + prints8(err, S("pkg-config: ")); + prints8(err, S("duplicate field '")); + prints8(err, r.dupname); + prints8(err, S("' in '")); + prints8(err, path); + prints8(err, S("'\n")); flush(err); os_fail(); - case Parse_OK: + case parse_OK: break; } r.pkg.path = path; r.pkg.realname = realname; - *insert(a, &r.pkg.env, S("pcfiledir")) = dirname(path); + s8 pcfiledir = s8pathencode(dirname(path), perm); + *insert(&r.pkg.env, S("pcfiledir"), perm) = pcfiledir; - Str missing = {0, 0}; + s8 missing = {0}; if (!r.pkg.name.s) { missing = S("Name"); } else if (!r.pkg.version.s) { @@ -1130,14 +1082,14 @@ static Pkg findpackage(Arena *a, Search *dirs, Out *err, Str realname) missing = S("Description"); } if (missing.s) { - outstr(err, S("pkg-config: ")); - outstr(err, S("missing field '")); - outstr(err, missing); - outstr(err, S("' in '")); - outstr(err, r.pkg.path); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("missing field '")); + prints8(err, missing); + prints8(err, S("' in '")); + prints8(err, r.pkg.path); + prints8(err, S("'\n")); flush(err); - #ifndef DEBUG + #ifndef FUZZTEST // Do not enforce during fuzzing os_fail(); #endif @@ -1147,17 +1099,18 @@ static Pkg findpackage(Arena *a, Search *dirs, Out *err, Str realname) } typedef struct { - Str arg; - Str tail; - Bool ok; -} DequoteResult; - -static Bool shellmeta(Byte c) -{ - // NOTE: matches pkg-config's listing, which excludes "$()" - Str meta = S("\"!#%&'*<>?[\\]`{|}"); - for (Size i = 0; i < meta.len; i++) { - if (meta.s[i] == c) { + s8 arg; + s8 tail; + b32 ok; +} dequoted; + +// Matches pkg-config's listing, which excludes "$()", but also match +// pathencode()ed bytes for escaping, which handles "$()" in paths. +static b32 shellmeta(u8 c) +{ + s8 meta = S("\"!#%&'*<>?[\\]`{|}"); + for (size i = 0; i < meta.len; i++) { + if (meta.s[i]==c || pathdecode(c)!=c) { return 1; } } @@ -1165,86 +1118,88 @@ static Bool shellmeta(Byte c) } // Process the next token. Return it and the unprocessed remainder. -static DequoteResult dequote(Arena *a, Str s) +static dequoted dequote(s8 s, arena *perm) { - Size i; - Byte quote = 0; - Bool escaped = 0; - Arena save = *a; - Out mem = newmembuf(a); + size i = 0; + u8 quote = 0; + b32 escaped = 0; + dequoted r = {0}; + arena rollback = *perm; + u8buf mem = newmembuf(perm); for (; s.len && whitespace(*s.s); s = cuthead(s, 1)) {} for (i = 0; i < s.len; i++) { - Byte c = s.s[i]; + u8 c = s.s[i]; if (whitespace(c)) { c = ' '; } + u8 decoded = pathdecode(c); if (quote == '\'') { if (c == '\'') { quote = 0; } else if (c==' ' || shellmeta(c)) { - outbyte(&mem, '\\'); - outbyte(&mem, c); + printu8(&mem, '\\'); + printu8(&mem, decoded); } else { - outbyte(&mem, c); + printu8(&mem, decoded); } } else if (quote == '"') { if (escaped) { escaped = 0; if (c!='\\' && c!='"') { - outbyte(&mem, '\\'); + printu8(&mem, '\\'); if (c==' ' || shellmeta(c)) { - outbyte(&mem, '\\'); + printu8(&mem, '\\'); } } - outbyte(&mem, c); + printu8(&mem, decoded); } else if (c == '\"') { quote = 0; } else if (c==' ' || shellmeta(c)) { - outbyte(&mem, '\\'); - outbyte(&mem, c); + printu8(&mem, '\\'); + printu8(&mem, decoded); } else { escaped = c == '\\'; - outbyte(&mem, c); + printu8(&mem, decoded); } } else if (c=='\'' || c=='"') { quote = c; } else if (shellmeta(c)) { - outbyte(&mem, '\\'); - outbyte(&mem, c); + printu8(&mem, '\\'); + printu8(&mem, decoded); } else if (c==' ') { break; } else { - outbyte(&mem, c); + printu8(&mem, c); } } if (quote) { - *a = save; - shredfree(a); - DequoteResult r = {0}; + *perm = rollback; return r; } - DequoteResult r = {finalize(&mem), cuthead(s, i), 1}; + r.arg = finalize(&mem); + r.tail = cuthead(s, i); + r.ok = 1; return r; } // Compare version strings, returning [-1, 0, +1]. Follows the RPM // version comparison specification like the original pkg-config. -static int compareversions(Str va, Str vb) +static i32 compareversions(s8 va, s8 vb) { - Size i = 0; + size i = 0; while (i pb.head.len) { @@ -1283,185 +1238,165 @@ static int compareversions(Str va, Str vb) } typedef enum { - VersionOp_ERR=0, - VersionOp_LT, - VersionOp_LTE, - VersionOp_EQ, - VersionOp_GTE, - VersionOp_GT -} VersionOp; + versop_ERR=0, + versop_LT, + versop_LTE, + versop_EQ, + versop_GTE, + versop_GT +} versop; -static VersionOp parseop(Str s) +static versop parseop(s8 s) { - if (equals(S("<"), s)) { - return VersionOp_LT; - } else if (equals(S("<="), s)) { - return VersionOp_LTE; - } else if (equals(S("="), s)) { - return VersionOp_EQ; - } else if (equals(S(">="), s)) { - return VersionOp_GTE; - } else if (equals(S(">"), s)) { - return VersionOp_GT; + if (s8equals(S("<"), s)) { + return versop_LT; + } else if (s8equals(S("<="), s)) { + return versop_LTE; + } else if (s8equals(S("="), s)) { + return versop_EQ; + } else if (s8equals(S(">="), s)) { + return versop_GTE; + } else if (s8equals(S(">"), s)) { + return versop_GT; } - return VersionOp_ERR; + return versop_ERR; } -static Str opname(VersionOp op) +static s8 opname(versop op) { switch (op) { - case VersionOp_ERR: break; - case VersionOp_LT: return S("<"); - case VersionOp_LTE: return S("<="); - case VersionOp_EQ: return S("="); - case VersionOp_GTE: return S(">="); - case VersionOp_GT: return S(">"); + case versop_ERR: break; + case versop_LT: return S("<"); + case versop_LTE: return S("<="); + case versop_EQ: return S("="); + case versop_GTE: return S(">="); + case versop_GT: return S(">"); } - ASSERT(0); - Str null = {0}; - return null; + assert(0); } -static Bool validcompare(VersionOp op, int result) +static b32 validcompare(versop op, i32 result) { switch (op) { - case VersionOp_ERR: break; - case VersionOp_LT: return result < 0; - case VersionOp_LTE: return result <= 0; - case VersionOp_EQ: return result == 0; - case VersionOp_GTE: return result >= 0; - case VersionOp_GT: return result > 0; - } - ASSERT(0); - return 0; + case versop_ERR: break; + case versop_LT: return result < 0; + case versop_LTE: return result <= 0; + case versop_EQ: return result == 0; + case versop_GTE: return result >= 0; + case versop_GT: return result > 0; + } + assert(0); } typedef struct { - Out *err; - Search search; - Env *global; - Pkgs *pkgs; - Pkg *last; - int maxdepth; - VersionOp op; - Bool define_prefix; - Bool recursive; - Bool ignore_versions; -} Processor; - -static Processor newprocessor(Config *c, Out *err, Env *g, Pkgs *pkgs) -{ - Arena *a = &c->arena; - Processor proc = {0}; - proc.err = err; - proc.search = newsearch(c->delim); - appendpath(a, &proc.search, c->envpath); - appendpath(a, &proc.search, c->fixedpath); - proc.global = g; - proc.pkgs = pkgs; - proc.maxdepth = (unsigned)-1 >> 1; - proc.define_prefix = 1; - proc.recursive = 1; + s8 arg; + pkg *last; + i32 depth; + i32 flags; + versop op; +} procstate; + +typedef struct { + u8buf *err; + search search; + env **global; + pkgs *pkgs; + pkg *last; + i32 maxdepth; + versop op; + b32 define_prefix; + b32 recursive; + b32 ignore_versions; + procstate stack[256]; +} processor; + +static processor *newprocessor(config *c, u8buf *err, env **g, pkgs *pkgs) +{ + arena *perm = &c->perm; + processor *proc = new(perm, processor, 1); + proc->err = err; + proc->search = newsearch(c->delim); + appendpath(&proc->search, c->envpath, perm); + appendpath(&proc->search, c->fixedpath, perm); + proc->global = g; + proc->pkgs = pkgs; + proc->maxdepth = (u32)-1 >> 1; + proc->define_prefix = 1; + proc->recursive = 1; return proc; } -static void procfail(Out *err, VersionOp op, Pkg *p) +static void procfail(u8buf *err, versop op, pkg *p) { - outstr(err, S("pkg-config: ")); - outstr(err, S("expected version following operator ")); - outstr(err, opname(op)); + prints8(err, S("pkg-config: ")); + prints8(err, S("expected version following operator ")); + prints8(err, opname(op)); if (p) { - outstr(err, S(" in package '")); - outstr(err, p->realname); - outstr(err, S("'")); + prints8(err, S(" in package '")); + prints8(err, p->realname); + prints8(err, S("'")); } - outstr(err, S("\n")); + prints8(err, S("\n")); flush(err); os_fail(); } -// Wrap the string in quotes if it contains whitespace. -static Str maybequote(Arena *a, Str s) +static void setprefix(pkg *p, arena *perm) { - for (Size i = 0; i < s.len; i++) { - if (whitespace(s.s[i])) { - Str r = newstr(a, s.len + 2); - Str t = copy(r, S("\"")); - t = copy(t, s); - copy(t, S("\"")); - return r; - } + s8 parent = dirname(p->path); + if (s8equals(S("pkgconfig"), basename(parent))) { + s8 prefix = dirname(dirname(parent)); + prefix = s8pathencode(prefix, perm); + *insert(&p->env, S("prefix"), perm) = prefix; } - return s; } -static void setprefix(Arena *a, Pkg *p) +static void failmaxrecurse(u8buf *err, s8 tok) { - Str parent = dirname(p->path); - if (equals(S("pkgconfig"), basename(parent))) { - Str prefix = dirname(dirname(parent)); - prefix = maybequote(a, prefix); - *insert(a, &p->env, S("prefix")) = prefix; - } -} - -typedef struct { - Str arg; - Pkg *last; - short depth; - short flags; - VersionOp op; -} ProcState; - -static void failmaxrecurse(Out *err, Str tok) -{ - outstr(err, S("pkg-config: ")); - outstr(err, S("exceeded max recursion depth on '")); - outstr(err, tok); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("exceeded max recursion depth on '")); + prints8(err, tok); + prints8(err, S("'\n")); flush(err); os_fail(); } -static void failversion(Out *err, Pkg *pkg, VersionOp op, Str want) +static void failversion(u8buf *err, pkg *pkg, versop op, s8 want) { - outstr(err, S("pkg-config: ")); - outstr(err, S("requested '")); - outstr(err, pkg->realname); - outstr(err, S("' ")); - outstr(err, opname(op)); - outstr(err, S(" '")); - outstr(err, want); - outstr(err, S("' but got '")); - outstr(err, pkg->version); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("requested '")); + prints8(err, pkg->realname); + prints8(err, S("' ")); + prints8(err, opname(op)); + prints8(err, S(" '")); + prints8(err, want); + prints8(err, S("' but got '")); + prints8(err, pkg->version); + prints8(err, S("'\n")); flush(err); os_fail(); } -static void process(Arena *a, Processor *proc, Str arg) +static void process(processor *proc, s8 arg, arena *perm) { - Out *err = proc->err; - Pkgs *pkgs = proc->pkgs; - Env *global = proc->global; - Search *search = &proc->search; + u8buf *err = proc->err; + pkgs *pkgs = proc->pkgs; + env **global = proc->global; + search *search = &proc->search; - // NOTE: At >=128, GCC generates a __chkstk_ms on x86-64 because the - // stack frame exceeds 4kB. A -mno-stack-arg-probe solves this, but - // limiting the recursion depth to 64, which is still plenty, avoids - // the issue entirely. - ProcState stack[64]; - int top = 0; + procstate *stack = proc->stack; + i32 cap = countof(proc->stack); + i32 top = 0; stack[0].arg = arg; stack[0].last = proc->last; stack[0].depth = 0; - stack[0].flags = Pkg_DIRECT | Pkg_PUBLIC; + stack[0].flags = pkg_DIRECT | pkg_PUBLIC; stack[0].op = proc->op; while (top >= 0) { - ProcState *s = stack + top; - StrPair pair = nexttoken(s->arg); - Str tok = pair.head; + procstate *s = stack + top; + s8pair pair = nexttoken(s->arg); + s8 tok = pair.head; if (!tok.len) { if (top>0 && s->op) { procfail(err, s->op, s->last); @@ -1473,83 +1408,83 @@ static void process(Arena *a, Processor *proc, Str arg) if (s->op) { if (!proc->ignore_versions) { - int cmp = compareversions(s->last->version, tok); + i32 cmp = compareversions(s->last->version, tok); if (!validcompare(s->op, cmp)) { failversion(err, s->last, s->op, tok); } } s->last = 0; - s->op = VersionOp_ERR; + s->op = versop_ERR; continue; } s->op = parseop(tok); if (s->op) { if (!s->last) { - outstr(err, S("pkg-config: ")); - outstr(err, S("unexpected operator '")); - outstr(err, tok); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("unexpected operator '")); + prints8(err, tok); + prints8(err, S("'\n")); flush(err); os_fail(); } continue; } - short depth = s->depth + 1; - short flags = s->flags; - Pkg *pkg = s->last = locate(a, pkgs, pathtorealname(tok)); - if (pkg->contents.s) { - if (flags&Pkg_PUBLIC && !(pkg->flags & Pkg_PUBLIC)) { + i32 depth = s->depth + 1; + i32 flags = s->flags; + pkg *p = s->last = locate(pkgs, pathtorealname(tok), perm); + if (p->contents.s) { + if (flags&pkg_PUBLIC && !(p->flags & pkg_PUBLIC)) { // We're on a public branch, but this package was // previously loaded as private. Recursively traverse // its public requires and mark all as public. - pkg->flags |= Pkg_PUBLIC; + p->flags |= pkg_PUBLIC; if (proc->recursive && depthmaxdepth) { - if (top >= COUNTOF(stack)-1) { + if (top >= cap-1) { failmaxrecurse(err, tok); } top++; - stack[top].arg = pkg->requires; + stack[top].arg = p->requires; stack[top].last = 0; stack[top].depth = depth; - stack[top].flags = flags & ~Pkg_DIRECT; - stack[top].op = VersionOp_ERR; + stack[top].flags = flags & ~pkg_DIRECT; + stack[top].op = versop_ERR; } } } else { // Package hasn't been loaded yet, so find and load it. - Pkg newpkg = findpackage(a, search, err, tok); + pkg newpkg = findpackage(search, err, tok, perm); if (proc->define_prefix) { - setprefix(a, &newpkg); + setprefix(&newpkg, perm); } - expandmerge(a, err, global, pkg, &newpkg); + expandmerge(err, *global, p, &newpkg, perm); if (proc->recursive && depthmaxdepth) { - if (top >= COUNTOF(stack)-2) { + if (top >= cap-2) { failmaxrecurse(err, tok); } top++; - stack[top].arg = pkg->requiresprivate; + stack[top].arg = p->requiresprivate; stack[top].last = 0; stack[top].depth = depth; stack[top].flags = 0; - stack[top].op = VersionOp_ERR; + stack[top].op = versop_ERR; top++; - stack[top].arg = pkg->requires; + stack[top].arg = p->requires; stack[top].last = 0; stack[top].depth = depth; - stack[top].flags = flags & ~Pkg_DIRECT; - stack[top].op = VersionOp_ERR; + stack[top].flags = flags & ~pkg_DIRECT; + stack[top].op = versop_ERR; } } - pkg->flags |= flags; + p->flags |= flags; } proc->last = stack[0].last; proc->op = stack[0].op; } -static void endprocessor(Processor *proc, Out *err) +static void endprocessor(processor *proc, u8buf *err) { if (proc->op) { procfail(err, proc->op, 0); @@ -1557,78 +1492,95 @@ static void endprocessor(Processor *proc, Out *err) } typedef enum { - Filter_ANY, - Filter_I, - Filter_L, - Filter_l, - Filter_OTHERC, - Filter_OTHERL -} Filter; + filter_ANY, + filter_I, + filter_L, + filter_l, + filter_OTHERC, + filter_OTHERL +} filter; -static Bool filterok(Filter f, Str arg) +static b32 filterok(filter f, s8 arg) { switch (f) { - case Filter_ANY: + case filter_ANY: return 1; - case Filter_I: + case filter_I: return startswith(arg, S("-I")); - case Filter_L: + case filter_L: return startswith(arg, S("-L")); - case Filter_l: + case filter_l: return startswith(arg, S("-l")); - case Filter_OTHERC: + case filter_OTHERC: return !startswith(arg, S("-I")); - case Filter_OTHERL: + case filter_OTHERL: return !startswith(arg, S("-L")) && !startswith(arg, S("-l")); } - ASSERT(0); - return 0; + assert(0); } -static void msvcize(Out *out, Str arg) +static void msvcize(u8buf *out, s8 arg) { if (startswith(arg, S("-L"))) { - outstr(out, S("/libpath:")); - outstr(out, cuthead(arg, 2)); + prints8(out, S("/libpath:")); + prints8(out, cuthead(arg, 2)); } else if (startswith(arg, S("-I"))) { - outstr(out, S("/I")); - outstr(out, cuthead(arg, 2)); + prints8(out, S("/I")); + prints8(out, cuthead(arg, 2)); } else if (startswith(arg, S("-l"))) { - outstr(out, cuthead(arg, 2)); - outstr(out, S(".lib")); + prints8(out, cuthead(arg, 2)); + prints8(out, S(".lib")); } else if (startswith(arg, S("-D"))) { - outstr(out, S("/D")); - outstr(out, cuthead(arg, 2)); - } else if (equals(arg, S("-mwindows"))) { - outstr(out, S("/subsystem:windows")); - } else if (equals(arg, S("-mconsole"))) { - outstr(out, S("/subsystem:console")); + prints8(out, S("/D")); + prints8(out, cuthead(arg, 2)); + } else if (s8equals(arg, S("-mwindows"))) { + prints8(out, S("/subsystem:windows")); + } else if (s8equals(arg, S("-mconsole"))) { + prints8(out, S("/subsystem:console")); } else { - outstr(out, arg); + prints8(out, arg); } } -typedef struct { - Treap node; - Size position; -} ArgsNode; +typedef struct argpos argpos; +struct argpos { + argpos *child[4]; + argpos *next; + s8 arg; + size position; +}; typedef struct { - StrList list; - Treap *map; - Size count; -} Args; + s8list list; + argpos *positions; + size count; +} args; -static Bool dedupable(Str arg) +static argpos *findargpos(argpos **m, s8 arg, arena *perm) +{ + for (u32 h = s8hash(arg); *m; h <<= 2) { + if (s8equals((*m)->arg, arg)) { + return *m; + } + m = &(*m)->child[h>>30]; + } + if (perm) { + *m = new(perm, argpos, 1); + (*m)->arg = arg; + } + return *m; +} + +static b32 dedupable(s8 arg) { // Do not count "-I" or "-L" with detached argument if (arg.len<3 || arg.s[0]!='-') { return 0; - } else if (equals(arg, S("-pthread"))) { + } else if (s8equals(arg, S("-pthread"))) { return 1; } - Str flags = S("DILflm"); - for (Size i = 0; i < flags.len; i++) { + s8 flags = S("DILflm"); + for (size i = 0; i < flags.len; i++) { if (arg.s[1] == flags.s[i]) { return 1; } @@ -1636,12 +1588,12 @@ static Bool dedupable(Str arg) return 0; } -static void appendarg(Arena *a, Args *args, Str arg) +static void appendarg(args *args, s8 arg, arena *perm) { - Size position = args->count++; - append(a, &args->list, arg); + append(&args->list, arg, perm); + size position = args->count++; if (dedupable(arg)) { - ArgsNode *n = (ArgsNode *)treapinsert(a, &args->map, arg, SIZEOF(*n)); + argpos *n = findargpos(&args->positions, arg, perm); if (!n->position || startswith(arg, S("-l"))) { // Zero position reserved for null, so bias it by 1 n->position = 1 + position; @@ -1649,58 +1601,61 @@ static void appendarg(Arena *a, Args *args, Str arg) } } -static void excludearg(Arena *a, Args *args, Str arg) +static void excludearg(args *args, s8 arg, arena *perm) { - ArgsNode *n = (ArgsNode *)treapinsert(a, &args->map, arg, SIZEOF(*n)); + argpos *n = findargpos(&args->positions, arg, perm); n->position = -1; // i.e. position before first argument } // Is this the correct position for the given argument? -static Bool inposition(Args *args, Str arg, Size position) +static b32 inposition(args *args, s8 arg, size position) { - ArgsNode *n = (ArgsNode *)treapinsert(0, &args->map, arg, SIZEOF(*n)); + argpos *n = findargpos(&args->positions, arg, 0); return !n || n->position==position+1; } typedef struct { - Arena *arena; - Size *argcount; - Args args; - Filter filter; - Bool msvc; - Byte delim; -} FieldWriter; - -static FieldWriter newfieldwriter(Arena *a, Filter filter, Size *argcount) -{ - FieldWriter w = {0}; - w.arena = a; - w.filter = filter; + arena *perm; + size *argcount; + args args; + filter filter; + b32 msvc; + u8 delim; +} fieldwriter; + +static fieldwriter newfieldwriter(filter f, size *argcount, arena *perm) +{ + fieldwriter w = {0}; + w.perm = perm; + w.filter = f; w.argcount = argcount; return w; } -static void insertsyspath(FieldWriter *w, Str path, Byte delim, Byte flag) +static void insertsyspath(fieldwriter *w, s8 path, u8 delim, u8 flag) { - Arena *a = w->arena; - Byte flagbuf[] = {'-', flag}; - Str prefix = {flagbuf, SIZEOF(flagbuf)}; + arena *perm = w->perm; + + u8 flagbuf[3] = {0}; + flagbuf[0] = '-'; + flagbuf[1] = flag; + s8 prefix = S(flagbuf); while (path.len) { - Cut c = cut(path, delim); - Str dir = c.head; + cut c = s8cut(path, delim); + s8 dir = c.head; path = c.tail; if (!dir.len) { continue; } // Prepend option flag - Str syspath = newstr(a, prefix.len+dir.len); - copy(copy(syspath, prefix), dir); + dir = s8pathencode(dir, perm); + s8 syspath = news8(perm, prefix.len+dir.len); + s8copy(s8copy(syspath, prefix), dir); // Process as an argument, as though being printed - syspath = maybequote(a, syspath); - DequoteResult dr = dequote(a, syspath); + dequoted dr = dequote(syspath, perm); syspath = dr.arg; // NOTE(NRK): Technically, the path doesn't need to follow the flag @@ -1712,56 +1667,56 @@ static void insertsyspath(FieldWriter *w, Str path, Byte delim, Byte flag) // practice, `pkgconf` which is used by many distros, also doesn't // handle it. if (dr.ok && !dr.tail.len) { - excludearg(a, &w->args, syspath); + excludearg(&w->args, syspath, perm); } } } -static void appendfield(Out *err, FieldWriter *w, Pkg *p, Str field) +static void appendfield(u8buf *err, fieldwriter *w, pkg *p, s8 field) { - Arena *a = w->arena; - Filter f = w->filter; + arena *perm = w->perm; + filter f = w->filter; while (field.len) { - DequoteResult r = dequote(a, field); + dequoted r = dequote(field, perm); if (!r.ok) { - outstr(err, S("pkg-config: ")); - outstr(err, S("unmatched quote in '")); - outstr(err, p->realname); - outstr(err, S("'\n")); + prints8(err, S("pkg-config: ")); + prints8(err, S("unmatched quote in '")); + prints8(err, p->realname); + prints8(err, S("'\n")); flush(err); os_fail(); } if (filterok(f, r.arg)) { - appendarg(a, &w->args, r.arg); + appendarg(&w->args, r.arg, perm); } field = r.tail; } } -static void writeargs(Out *out, FieldWriter *w) +static void writeargs(u8buf *out, fieldwriter *w) { - Size position = 0; - Byte delim = w->delim ? w->delim : ' '; - for (StrListNode *n = w->args.list.head; n; n = n->next) { - Str arg = n->entry; + size position = 0; + u8 delim = w->delim ? w->delim : ' '; + for (s8node *n = w->args.list.head; n; n = n->next) { + s8 arg = n->str; if (inposition(&w->args, arg, position++)) { if ((*w->argcount)++) { - outbyte(out, delim); + printu8(out, delim); } if (w->msvc) { msvcize(out, arg); } else { - outstr(out, arg); + prints8(out, arg); } } } } -static int parseuint(Str s, int hi) +static i32 parseuint(s8 s, i32 hi) { - int v = 0; - for (Size i = 0; i < s.len; i++) { - Byte c = s.s[i]; + i32 v = 0; + for (size i = 0; i < s.len; i++) { + u8 c = s.s[i]; if (digit(c)) { v = v*10 + c - '0'; if (v >= hi) { @@ -1772,50 +1727,52 @@ static int parseuint(Str s, int hi) return v; } -static void appmain(Config conf) -{ - Arena *a = &conf.arena; - shredfree(a); - - Env global = {0}; - Filter filterc = Filter_ANY; - Filter filterl = Filter_ANY; - Pkgs pkgs = {0}; - Out out = newoutput(a, 1, 1<<12); - Out err = newoutput(a, 2, 1<<7); - Processor proc = newprocessor(&conf, &err, &global, &pkgs); - Size argcount = 0; - - Bool msvc = 0; - Bool libs = 0; - Bool cflags = 0; - Bool err_to_stdout = 0; - Bool silent = 0; - Bool static_ = 0; - Byte argdelim = ' '; - Bool modversion = 0; - VersionOp override_op = VersionOp_ERR; - Str override_version = {0, 0}; - Bool print_sysinc = 0; - Bool print_syslib = 0; - Str variable = {0, 0}; - - proc.define_prefix = conf.define_prefix; - if (!conf.top_builddir.s) { - conf.top_builddir = S("$(top_builddir)"); - } - - *insert(a, &global, S("pc_path")) = conf.fixedpath; - *insert(a, &global, S("pc_system_includedirs")) = conf.sys_incpath; - *insert(a, &global, S("pc_system_libdirs")) = conf.sys_libpath; - *insert(a, &global, S("pc_sysrootdir")) = S("/"); - *insert(a, &global, S("pc_top_builddir")) = conf.top_builddir; - - Str *args = (Str *)allocarray(a, SIZEOF(Str), conf.nargs); - Size nargs = 0; - - for (OptionParser opts = newoptionparser(conf.args, conf.nargs);;) { - OptionResult r = nextoption(&opts); +static void uconfig(config *conf) +{ + arena *perm = &conf->perm; + + env *global = 0; + filter filterc = filter_ANY; + filter filterl = filter_ANY; + pkgs *pkgs = newpkgs(perm); + u8buf *out = newfdbuf(perm, 1, 1<<12); + u8buf *err = newfdbuf(perm, 2, 1<<7); + processor *proc = newprocessor(conf, err, &global, pkgs); + size argcount = 0; + + b32 msvc = 0; + b32 libs = 0; + b32 cflags = 0; + b32 err_to_stdout = 0; + b32 silent = 0; + b32 static_ = 0; + u8 argdelim = ' '; + b32 modversion = 0; + versop override_op = versop_ERR; + s8 override_version = {0}; + b32 print_sysinc = !!conf->print_sysinc.s; + b32 print_syslib = !!conf->print_syslib.s; + s8 variable = {0}; + + proc->define_prefix = conf->define_prefix; + s8 top_builddir = conf->top_builddir; + if (top_builddir.s) { + top_builddir = s8pathencode(top_builddir, perm); + } else { + top_builddir = S("$(top_builddir)"); + } + + *insert(&global, S("pc_path"), perm) = conf->fixedpath; + *insert(&global, S("pc_system_includedirs"), perm) = conf->sys_incpath; + *insert(&global, S("pc_system_libdirs"), perm) = conf->sys_libpath; + *insert(&global, S("pc_sysrootdir"), perm) = S("/"); + *insert(&global, S("pc_top_builddir"), perm) = top_builddir; + + s8 *args = new(perm, s8, conf->nargs); + size nargs = 0; + + for (options opts = newoptions(conf->args, conf->nargs);;) { + optresult r = nextoption(&opts); if (!r.ok) { break; } @@ -1823,167 +1780,167 @@ static void appmain(Config conf) if (!r.isoption) { args[nargs++] = r.arg; - } else if (equals(r.arg, S("h")) || equals(r.arg, S("-help"))) { - usage(&out); - flush(&out); + } else if (s8equals(r.arg, S("h")) || s8equals(r.arg, S("-help"))) { + usage(out); + flush(out); return; - } else if (equals(r.arg, S("-version"))) { - outstr(&out, S(VERSION)); - outbyte(&out, '\n'); - flush(&out); + } else if (s8equals(r.arg, S("-version"))) { + prints8(out, S(VERSION)); + printu8(out, '\n'); + flush(out); return; - } else if (equals(r.arg, S("-modversion"))) { + } else if (s8equals(r.arg, S("-modversion"))) { modversion = 1; - } else if (equals(r.arg, S("-define-prefix"))) { - proc.define_prefix = 1; + } else if (s8equals(r.arg, S("-define-prefix"))) { + proc->define_prefix = 1; - } else if (equals(r.arg, S("-dont-define-prefix"))) { - proc.define_prefix = 0; + } else if (s8equals(r.arg, S("-dont-define-prefix"))) { + proc->define_prefix = 0; - } else if (equals(r.arg, S("-cflags"))) { + } else if (s8equals(r.arg, S("-cflags"))) { cflags = 1; - filterc = Filter_ANY; + filterc = filter_ANY; - } else if (equals(r.arg, S("-libs"))) { + } else if (s8equals(r.arg, S("-libs"))) { libs = 1; - filterl = Filter_ANY; + filterl = filter_ANY; - } else if (equals(r.arg, S("-variable"))) { + } else if (s8equals(r.arg, S("-variable"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } variable = r.value; - } else if (equals(r.arg, S("-static"))) { + } else if (s8equals(r.arg, S("-static"))) { static_ = 1; - } else if (equals(r.arg, S("-libs-only-L"))) { + } else if (s8equals(r.arg, S("-libs-only-L"))) { libs = 1; - filterl = Filter_L; + filterl = filter_L; - } else if (equals(r.arg, S("-libs-only-l"))) { + } else if (s8equals(r.arg, S("-libs-only-l"))) { libs = 1; - filterl = Filter_l; + filterl = filter_l; - } else if (equals(r.arg, S("-libs-only-other"))) { + } else if (s8equals(r.arg, S("-libs-only-other"))) { libs = 1; - filterl = Filter_OTHERL; + filterl = filter_OTHERL; - } else if (equals(r.arg, S("-cflags-only-I"))) { + } else if (s8equals(r.arg, S("-cflags-only-I"))) { cflags = 1; - filterc = Filter_I; + filterc = filter_I; - } else if (equals(r.arg, S("-cflags-only-other"))) { + } else if (s8equals(r.arg, S("-cflags-only-other"))) { cflags = 1; - filterc = Filter_OTHERC; + filterc = filter_OTHERC; - } else if (equals(r.arg, S("-with-path"))) { + } else if (s8equals(r.arg, S("-with-path"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - prependpath(a, &proc.search, r.value); + prependpath(&proc->search, r.value, perm); - } else if (equals(r.arg, S("-maximum-traverse-depth"))) { + } else if (s8equals(r.arg, S("-maximum-traverse-depth"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - proc.maxdepth = parseuint(r.value, 1000); + proc->maxdepth = parseuint(r.value, 1000); - } else if (equals(r.arg, S("-msvc-syntax"))) { + } else if (s8equals(r.arg, S("-msvc-syntax"))) { msvc = 1; - } else if (equals(r.arg, S("-define-variable"))) { + } else if (s8equals(r.arg, S("-define-variable"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - Cut c = cut(r.value, '='); + cut c = s8cut(r.value, '='); if (!c.ok) { - outstr(&err, S("pkg-config: ")); - outstr(&err, S("value missing in --define-variable for '")); - outstr(&err, r.value); - outstr(&err, S("'\n")); - flush(&err); + prints8(err, S("pkg-config: ")); + prints8(err, S("value missing in --define-variable for '")); + prints8(err, r.value); + prints8(err, S("'\n")); + flush(err); os_fail(); } - *insert(a, &global, c.head) = c.tail; + *insert(&global, c.head, perm) = c.tail; - } else if (equals(r.arg, S("-newlines"))) { + } else if (s8equals(r.arg, S("-newlines"))) { argdelim = '\n'; - } else if (equals(r.arg, S("-exists"))) { + } else if (s8equals(r.arg, S("-exists"))) { // The check already happens, just disable the messages silent = 1; - } else if (equals(r.arg, S("-atleast-pkgconfig-version"))) { + } else if (s8equals(r.arg, S("-atleast-pkgconfig-version"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } return; // always succeeds - } else if (equals(r.arg, S("-atleast-version"))) { + } else if (s8equals(r.arg, S("-atleast-version"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - override_op = VersionOp_GTE; + override_op = versop_GTE; override_version = r.value; silent = 1; - proc.recursive = 0; - proc.ignore_versions = 1; + proc->recursive = 0; + proc->ignore_versions = 1; - } else if (equals(r.arg, S("-exact-version"))) { + } else if (s8equals(r.arg, S("-exact-version"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - override_op = VersionOp_EQ; + override_op = versop_EQ; override_version = r.value; silent = 1; - proc.recursive = 0; - proc.ignore_versions = 1; + proc->recursive = 0; + proc->ignore_versions = 1; - } else if (equals(r.arg, S("-max-version"))) { + } else if (s8equals(r.arg, S("-max-version"))) { if (!r.value.s) { - r.value = getargopt(&err, &opts, r.arg); + r.value = getargopt(err, &opts, r.arg); } - override_op = VersionOp_LTE; + override_op = versop_LTE; override_version = r.value; silent = 1; - proc.recursive = 0; - proc.ignore_versions = 1; + proc->recursive = 0; + proc->ignore_versions = 1; - } else if (equals(r.arg, S("-silence-errors"))) { + } else if (s8equals(r.arg, S("-silence-errors"))) { silent = 1; - } else if (equals(r.arg, S("-errors-to-stdout"))) { + } else if (s8equals(r.arg, S("-errors-to-stdout"))) { err_to_stdout = 1; - } else if (equals(r.arg, S("-print-errors"))) { + } else if (s8equals(r.arg, S("-print-errors"))) { // Ignore - } else if (equals(r.arg, S("-short-errors"))) { + } else if (s8equals(r.arg, S("-short-errors"))) { // Ignore - } else if (equals(r.arg, S("-uninstalled"))) { + } else if (s8equals(r.arg, S("-uninstalled"))) { // Ignore - } else if (equals(r.arg, S("-keep-system-cflags"))) { + } else if (s8equals(r.arg, S("-keep-system-cflags"))) { print_sysinc = 1; - } else if (equals(r.arg, S("-keep-system-libs"))) { + } else if (s8equals(r.arg, S("-keep-system-libs"))) { print_syslib = 1; - } else if (equals(r.arg, S("-validate"))) { + } else if (s8equals(r.arg, S("-validate"))) { silent = 1; - proc.recursive = 0; + proc->recursive = 0; } else { - outstr(&err, S("pkg-config: ")); - outstr(&err, S("unknown option -")); - outstr(&err, r.arg); - outstr(&err, S("\n")); - flush(&err); + prints8(err, S("pkg-config: ")); + prints8(err, S("unknown option -")); + prints8(err, r.arg); + prints8(err, S("\n")); + flush(err); os_fail(); } } @@ -1993,94 +1950,135 @@ static void appmain(Config conf) } if (silent) { - err = newnullout(); + err = newnullout(perm); } - for (Size i = 0; i < nargs; i++) { - process(a, &proc, args[i]); + for (size i = 0; i < nargs; i++) { + process(proc, args[i], perm); } - endprocessor(&proc, &err); + endprocessor(proc, err); - if (!pkgs.count) { - outstr(&err, S("pkg-config: ")); - outstr(&err, S("requires at least one package name\n")); - flush(&err); + if (!pkgs->count) { + prints8(err, S("pkg-config: ")); + prints8(err, S("requires at least one package name\n")); + flush(err); os_fail(); } // --{atleast,exact,max}-version if (override_op) { - for (Pkg *p = pkgs.head; p; p = p->list) { - int cmp = compareversions(p->version, override_version); + for (pkg *p = pkgs->head; p; p = p->list) { + i32 cmp = compareversions(p->version, override_version); if (!validcompare(override_op, cmp)) { - failversion(&err, p, override_op, override_version); + failversion(err, p, override_op, override_version); } } } if (modversion) { - for (Pkg *p = pkgs.head; p; p = p->list) { - if (p->flags & Pkg_DIRECT) { - outstr(&out, p->version); - outstr(&out, S("\n")); + for (pkg *p = pkgs->head; p; p = p->list) { + if (p->flags & pkg_DIRECT) { + prints8(out, p->version); + prints8(out, S("\n")); } } } if (variable.s) { - for (Pkg *p = pkgs.head; p; p = p->list) { - if (p->flags & Pkg_DIRECT) { - Str value = lookup(&global, &p->env, variable); + for (pkg *p = pkgs->head; p; p = p->list) { + if (p->flags & pkg_DIRECT) { + s8 value = lookup(global, p->env, variable); if (value.s) { - expand(&out, &err, &global, p, value); - outstr(&out, S("\n")); + expand(out, err, global, p, value); + prints8(out, S("\n")); } } } } if (cflags) { - Arena temp = *a; // auto-free when done - FieldWriter fw = newfieldwriter(&temp, filterc, &argcount); + arena scratch = *perm; + fieldwriter fw = newfieldwriter(filterc, &argcount, &scratch); fw.delim = argdelim; fw.msvc = msvc; if (!print_sysinc) { - insertsyspath(&fw, conf.sys_incpath, conf.delim, 'I'); + insertsyspath(&fw, conf->sys_incpath, conf->delim, 'I'); } - for (Pkg *p = pkgs.head; p; p = p->list) { - appendfield(&err, &fw, p, p->cflags); + for (pkg *p = pkgs->head; p; p = p->list) { + appendfield(err, &fw, p, p->cflags); } - writeargs(&out, &fw); + writeargs(out, &fw); } if (libs) { - Arena temp = *a; // auto-free when done - FieldWriter fw = newfieldwriter(&temp, filterl, &argcount); + arena scratch = *perm; + fieldwriter fw = newfieldwriter(filterl, &argcount, &scratch); fw.delim = argdelim; fw.msvc = msvc; if (!print_syslib) { - insertsyspath(&fw, conf.sys_libpath, conf.delim, 'L'); + insertsyspath(&fw, conf->sys_libpath, conf->delim, 'L'); } - for (Pkg *p = pkgs.head; p; p = p->list) { + for (pkg *p = pkgs->head; p; p = p->list) { if (static_) { - appendfield(&err, &fw, p, p->libs); - appendfield(&err, &fw, p, p->libsprivate); - } else if (p->flags & Pkg_PUBLIC) { - appendfield(&err, &fw, p, p->libs); + appendfield(err, &fw, p, p->libs); + appendfield(err, &fw, p, p->libsprivate); + } else if (p->flags & pkg_PUBLIC) { + appendfield(err, &fw, p, p->libs); } } - writeargs(&out, &fw); + writeargs(out, &fw); } if (cflags || libs) { - outstr(&out, S("\n")); + prints8(out, S("\n")); } - flush(&out); + flush(out); } +// Win32 types, constants, and declarations (replaces windows.h) +// This is free and unencumbered software released into the public domain. + +typedef __SIZE_TYPE__ uptr; +typedef unsigned short char16_t; +typedef char16_t c16; + +enum { + FILE_ATTRIBUTE_NORMAL = 0x80, + + FILE_SHARE_ALL = 7, + + GENERIC_READ = 0x80000000, + + INVALID_HANDLE_VALUE = -1, + + MEM_COMMIT = 0x1000, + MEM_RESERVE = 0x2000, + + OPEN_EXISTING = 3, + + PAGE_READWRITE = 4, + + STD_OUTPUT_HANDLE = -11, + STD_ERROR_HANDLE = -12, +}; + +#define W32(r) __declspec(dllimport) r __stdcall +W32(b32) CloseHandle(uptr); +W32(i32) CreateFileW(c16 *, i32, i32, uptr, i32, i32, i32); +W32(void) ExitProcess(i32); +W32(c16 *) GetCommandLineW(void); +W32(b32) GetConsoleMode(uptr, i32 *); +W32(i32) GetEnvironmentVariableW(c16 *, c16 *, i32); +W32(i32) GetModuleFileNameW(uptr, c16 *, i32); +W32(i32) GetStdHandle(i32); +W32(b32) ReadFile(uptr, u8 *, i32, i32 *, uptr); +W32(byte *) VirtualAlloc(uptr, size, i32, i32); +W32(b32) WriteConsoleW(uptr, c16 *, i32, i32 *, uptr); +W32(b32) WriteFile(uptr, u8 *, i32, i32 *, uptr); + #define CMDLINE_CMD_MAX 32767 // max command line length on Windows -#define CMDLINE_ARGV_MAX (16384+(98298+(int)sizeof(char*))/(int)sizeof(char*)) +#define CMDLINE_ARGV_MAX (16384+(98298+(i32)sizeof(u8 *))/(i32)sizeof(u8 *)) // Convert an ill-formed-UTF-16 command line to a WTF-8 argv following // field splitting semantics identical to CommandLineToArgvW, including @@ -2098,17 +2096,17 @@ static void appmain(Config conf) // test if cmd[0] is zero and then act accordingly. // // If the input is UTF-16, then the output is UTF-8. -static int cmdline_to_argv8(unsigned short *cmd, char **argv) +static i32 cmdline_to_argv8(c16 *cmd, u8 **argv) { - int argc = 1; // worst case: argv[0] is an empty string - int state = 6; // special argv[0] state - int slash = 0; + i32 argc = 1; // worst case: argv[0] is an empty string + i32 state = 6; // special argv[0] state + i32 slash = 0; // Use second half as byte buffer - unsigned char *buf = (unsigned char *)(argv + 16384); + u8 *buf = (u8 *)(argv + 16384); - argv[0] = (char *)buf; + argv[0] = buf; while (*cmd) { - int c = *cmd++; + i32 c = *cmd++; if (c>>10 == 0x36 && *cmd>>10 == 0x37) { // surrogates? c = 0x10000 + ((c - 0xd800)<<10) + (*cmd++ - 0xdc00); } @@ -2117,14 +2115,14 @@ static int cmdline_to_argv8(unsigned short *cmd, char **argv) case 0: switch (c) { // outside token case 0x09: case 0x20: continue; - case 0x22: argv[argc++] = (char *)buf; + case 0x22: argv[argc++] = buf; state = 2; continue; - case 0x5c: argv[argc++] = (char *)buf; + case 0x5c: argv[argc++] = buf; slash = 1; state = 3; break; - default : argv[argc++] = (char *)buf; + default : argv[argc++] = buf; state = 1; } break; case 1: switch (c) { // inside unquoted token @@ -2187,16 +2185,16 @@ static int cmdline_to_argv8(unsigned short *cmd, char **argv) // WTF-8/UTF-8 encoding switch ((c >= 0x80) + (c >= 0x800) + (c >= 0x10000)) { - case 0: *buf++ = 0x00 | ((char)(c >> 0) ); break; - case 1: *buf++ = 0xc0 | ((char)(c >> 6) ); - *buf++ = 0x80 | ((char)(c >> 0) & 63); break; - case 2: *buf++ = 0xe0 | ((char)(c >> 12) ); - *buf++ = 0x80 | ((char)(c >> 6) & 63); - *buf++ = 0x80 | ((char)(c >> 0) & 63); break; - case 3: *buf++ = 0xf0 | ((char)(c >> 18) ); - *buf++ = 0x80 | ((char)(c >> 12) & 63); - *buf++ = 0x80 | ((char)(c >> 6) & 63); - *buf++ = 0x80 | ((char)(c >> 0) & 63); break; + case 0: *buf++ = (u8)(0x00 | ((c >> 0) )); break; + case 1: *buf++ = (u8)(0xc0 | ((c >> 6) )); + *buf++ = (u8)(0x80 | ((c >> 0) & 63)); break; + case 2: *buf++ = (u8)(0xe0 | ((c >> 12) )); + *buf++ = (u8)(0x80 | ((c >> 6) & 63)); + *buf++ = (u8)(0x80 | ((c >> 0) & 63)); break; + case 3: *buf++ = (u8)(0xf0 | ((c >> 18) )); + *buf++ = (u8)(0x80 | ((c >> 12) & 63)); + *buf++ = (u8)(0x80 | ((c >> 6) & 63)); + *buf++ = (u8)(0x80 | ((c >> 0) & 63)); break; } } @@ -2205,355 +2203,477 @@ static int cmdline_to_argv8(unsigned short *cmd, char **argv) return argc; } -// Win32 API: windows.h replacement, halves build times +// Mingw-w64 Win32 platform layer for u-config +// $ cc -nostartfiles -o pkg-config win32_main.c +// This is free and unencumbered software released into the public domain. -typedef int BOOL; -typedef void *HANDLE; -typedef unsigned DWORD; -#if __GNUC__ // in MSVC size_t is a built-in type - typedef __SIZE_TYPE__ size_t; -#endif -#if !__cplusplus || (_MSC_VER && !_NATIVE_WCHAR_T_DEFINED) - // NOTE: wchar_t is a built-in type in C++, except older versions of - // Visual Studio are not so C++-compliant without /Zc:wchar_t. - typedef unsigned short wchar_t; +#ifndef PKG_CONFIG_PREFIX +# define PKG_CONFIG_PREFIX #endif -#define CP_UTF8 65001 - -#define FILE_ATTRIBUTE_NORMAL 0x80 - -#define FILE_MAP_READ 4 - -#define FILE_SHARE_DELETE 4 -#define FILE_SHARE_READ 1 -#define FILE_SHARE_WRITE 2 +// For communication with os_write() +static struct { + i32 handle; + b32 isconsole; + b32 err; +} handles[3]; -#define GENERIC_READ 0x80000000 - -#define INVALID_HANDLE_VALUE ((HANDLE)-1) - -#define MAX_PATH 260 +typedef struct { + c16 *s; + size len; +} s16; -#define MEM_COMMIT 0x1000 -#define MEM_RESERVE 0x2000 +static s16 s16cuthead_(s16 s, size off) +{ + assert(off >= 0); + assert(off <= s.len); + s.s += off; + s.len -= off; + return s; +} -#define OPEN_EXISTING 3 +static arena newarena_(size cap) +{ + arena arena = {0}; + arena.beg = VirtualAlloc(0, cap, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); + if (!arena.beg) { + arena.beg = (byte *)16; // aligned, non-null, zero-size arena + cap = 0; + } + arena.end = arena.beg + cap; + return arena; +} -#define PAGE_READONLY 2 -#define PAGE_READWRITE 4 +typedef i32 char32_t; +typedef char32_t c32; -#define STD_ERROR_HANDLE -12 -#define STD_OUTPUT_HANDLE -11 +enum { + REPLACEMENT_CHARACTER = 0xfffd +}; -#ifdef __cplusplus -extern "C" { -#endif -__declspec(dllimport) HANDLE __stdcall CreateFileW( - wchar_t *, DWORD, DWORD, void *, DWORD, DWORD, HANDLE); -__declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE); -__declspec(dllimport) HANDLE __stdcall CreateFileMappingA( - HANDLE, void *, DWORD, DWORD, DWORD, char *); -__declspec(dllimport) int __stdcall MultiByteToWideChar( - unsigned, DWORD, char *, int, wchar_t *, int); -__declspec(dllimport) wchar_t *__stdcall GetCommandLineW(void); -__declspec(dllimport) BOOL __stdcall GetConsoleMode(HANDLE, DWORD *); -__declspec(dllimport) DWORD __stdcall GetEnvironmentVariableW( - const wchar_t *, wchar_t *, DWORD); -__declspec(dllimport) DWORD __stdcall GetFileSize(HANDLE, DWORD *); -__declspec(dllimport) DWORD __stdcall GetModuleFileNameW( - HANDLE, wchar_t *, DWORD); -__declspec(dllimport) HANDLE __stdcall GetStdHandle(DWORD); -__declspec(dllimport) void *__stdcall MapViewOfFile( - HANDLE, DWORD, DWORD, DWORD, size_t); -__declspec(dllimport) void *__stdcall VirtualAlloc( - void *, size_t, DWORD, DWORD); -__declspec(dllimport) int __stdcall WideCharToMultiByte( - unsigned, DWORD, wchar_t *, int, char *, int, char *, BOOL *); -__declspec(dllimport) BOOL __stdcall WriteConsoleW( - HANDLE, wchar_t *, DWORD, DWORD *, void *); -__declspec(dllimport) BOOL __stdcall WriteFile( - HANDLE, void *, DWORD, DWORD *, void *); -__declspec(dllimport) __declspec(noreturn) void __stdcall ExitProcess(DWORD); -#ifdef __cplusplus +typedef struct { + s8 tail; + c32 rune; +} utf8; + +static utf8 utf8decode_(s8 s) +{ + assert(s.len); + utf8 r = {0}; + switch (s.s[0]&0xf0) { + default : r.rune = s.s[0]; + if (r.rune > 0x7f) break; + r.tail = cuthead(s, 1); + return r; + case 0xc0: + case 0xd0: if (s.len < 2) break; + if ((s.s[1]&0xc0) != 0x80) break; + r.rune = (i32)(s.s[0]&0x1f) << 6 | + (i32)(s.s[1]&0x3f) << 0; + if (r.rune < 0x80) break; + r.tail = cuthead(s, 2); + return r; + case 0xe0: if (s.len < 3) break; + if ((s.s[1]&0xc0) != 0x80) break; + if ((s.s[2]&0xc0) != 0x80) break; + r.rune = (i32)(s.s[0]&0x0f) << 12 | + (i32)(s.s[1]&0x3f) << 6 | + (i32)(s.s[2]&0x3f) << 0; + if (r.rune < 0x800) break; + if (r.rune>=0xd800 && r.rune<=0xdfff) break; + r.tail = cuthead(s, 3); + return r; + case 0xf0: if (s.len < 4) break; + if ((s.s[1]&0xc0) != 0x80) break; + if ((s.s[2]&0xc0) != 0x80) break; + if ((s.s[3]&0xc0) != 0x80) break; + r.rune = (i32)(s.s[0]&0x0f) << 18 | + (i32)(s.s[1]&0x3f) << 12 | + (i32)(s.s[2]&0x3f) << 6 | + (i32)(s.s[3]&0x3f) << 0; + if (r.rune < 0x10000) break; + if (r.rune > 0x10ffff) break; + r.tail = cuthead(s, 4); + return r; + } + r.rune = REPLACEMENT_CHARACTER; + r.tail = cuthead(s, 1); + return r; } -#endif -// Win32 platform layer for u-config -// This is free and unencumbered software released into the public domain. +// Encode code point returning the output length (1-4). +static i32 utf8encode_(u8 *s, c32 rune) +{ + if (rune<0 || (rune>=0xd800 && rune<=0xdfff) || rune>0x10ffff) { + rune = REPLACEMENT_CHARACTER; + } + switch ((rune >= 0x80) + (rune >= 0x800) + (rune >= 0x10000)) { + case 0: s[0] = (u8)(0x00 | ((rune >> 0) )); return 1; + case 1: s[0] = (u8)(0xc0 | ((rune >> 6) )); + s[1] = (u8)(0x80 | ((rune >> 0) & 63)); return 2; + case 2: s[0] = (u8)(0xe0 | ((rune >> 12) )); + s[1] = (u8)(0x80 | ((rune >> 6) & 63)); + s[2] = (u8)(0x80 | ((rune >> 0) & 63)); return 3; + case 3: s[0] = (u8)(0xf0 | ((rune >> 18) )); + s[1] = (u8)(0x80 | ((rune >> 12) & 63)); + s[2] = (u8)(0x80 | ((rune >> 6) & 63)); + s[3] = (u8)(0x80 | ((rune >> 0) & 63)); return 4; + } + assert(0); +} -#ifndef PKG_CONFIG_PREFIX -# define PKG_CONFIG_PREFIX -#endif +typedef struct { + s16 tail; + c32 rune; +} utf16; + +static utf16 utf16decode_(s16 s) +{ + assert(s.len); + utf16 r = {0}; + r.rune = s.s[0]; + if (r.rune>=0xdc00 && r.rune<=0xdfff) { + goto reject; // unpaired low surrogate + } else if (r.rune>=0xd800 && r.rune<=0xdbff) { + if (s.len < 2) { + goto reject; // missing low surrogate + } + i32 hi = r.rune; + i32 lo = s.s[1]; + if (lo<0xdc00 || lo>0xdfff) { + goto reject; // expected low surrogate + } + r.rune = 0x10000 + ((hi - 0xd800)<<10) + (lo - 0xdc00); + r.tail = s16cuthead_(s, 2); + return r; + } + r.tail = s16cuthead_(s, 1); + return r; -#ifdef _MSC_VER - #ifdef __cplusplus - #define EXTERN extern "C" - #else - #define EXTERN - #endif - #define ENTRYPOINT EXTERN - #pragma comment(lib, "kernel32.lib") - #pragma comment(linker, "/subsystem:console") - EXTERN void *memset(void *, int, size_t); - #pragma function(memset) - EXTERN void *memset(void *d, int c, size_t n) - { - char *dst = (char *)d; - for (; n; n--) *dst++ = (char)c; - return d; - } - EXTERN void *memcpy(void *, const void *, size_t); - #pragma function(memcpy) - EXTERN void *memcpy(void *d, const void *s, size_t n) - { - char *dst = (char *)d; - char *src = (char *)s; - for (; n; n--) *dst++ = *src++; - return d; - } -#elif __GNUC__ - #ifdef __cplusplus - #define EXTERN extern "C" __attribute__((externally_visible)) - #else - #define EXTERN __attribute__((externally_visible)) - #endif - #if __i686__ - #define ENTRYPOINT EXTERN __attribute__((force_align_arg_pointer)) - #else - #define ENTRYPOINT EXTERN - #endif - // NOTE: These functions are required at higher GCC optimization - // levels. Placing them in their own section allows them to be - // ommitted via -Wl,--gc-sections when unused. - EXTERN - __attribute__((section(".text.memcpy"))) - void *memcpy(void *d, const void *s, size_t n) - { - // NOTE: polyglot x86 and x64 inline assembly - void *r = d; - __asm volatile ( - "rep movsb" - : "=D"(d), "=S"(s), "=c"(n) - : "0"(d), "1"(s), "2"(n) - : "memory" - ); - return r; - } - EXTERN - __attribute__((section(".text.strlen"))) - size_t strlen(const char *s) - { - const char *b = s; - __asm("repne scasb" : "=D"(s) : "0"(s), "a"(0), "c"((size_t)-1)); - return s - b - 1; - } -#endif + reject: + r.rune = REPLACEMENT_CHARACTER; + r.tail = s16cuthead_(s, 1); + return r; +} -static Bool error_is_console = 0; +// Encode code point returning the output length (1-2). +static i32 utf16encode_(c16 *dst, c32 rune) +{ + if (rune<0 || (rune>=0xd800 && rune<=0xdfff) || rune>0x10ffff) { + rune = REPLACEMENT_CHARACTER; + } + if (rune >= 0x10000) { + rune -= 0x10000; + dst[0] = (c16)((rune >> 10) + 0xd800); + dst[1] = (c16)((rune&0x3ff) + 0xdc00); + return 2; + } + dst[0] = (c16)rune; + return 1; +} -static Arena newarena_(void) +static s16 towide_(arena *perm, s8 s) { - Arena arena = {0}; - Size cap = 1<<28; - #if DEBUG - cap = 1<<21; - #endif - DWORD type = MEM_COMMIT | MEM_RESERVE; - arena.mem.s = (Byte *)VirtualAlloc(0, cap, type, PAGE_READWRITE); - arena.mem.len = arena.mem.s ? cap : 0; - shredfree(&arena); - return arena; + size len = 0; + utf8 state = {0}; + state.tail = s; + while (state.tail.len) { + state = utf8decode_(state.tail); + c16 tmp[2]; + len += utf16encode_(tmp, state.rune); + } + + s16 w = {0}; + w.s = new(perm, c16, len); + state.tail = s; + while (state.tail.len) { + state = utf8decode_(state.tail); + w.len += utf16encode_(w.s+w.len, state.rune); + } + return w; } -static Str fromwide_(Arena *a, wchar_t *w, Size wlen) +static s8 fromwide_(arena *perm, s16 w) { - // NOTE: consider replacing the Win32 UTF-8 encoder/decoder with an - // embedded WTF-8 encoder/decoder - int len = WideCharToMultiByte(CP_UTF8, 0, w, wlen, 0, 0, 0, 0); - Str s = newstr(a, len); - WideCharToMultiByte(CP_UTF8, 0, w, wlen, (char *)s.s, s.len, 0, 0); + size len = 0; + utf16 state = {0}; + state.tail = w; + while (state.tail.len) { + state = utf16decode_(state.tail); + u8 tmp[4]; + len += utf8encode_(tmp, state.rune); + } + + s8 s = {0}; + s.s = new(perm, u8, len); + state.tail = w; + while (state.tail.len) { + state = utf16decode_(state.tail); + s.len += utf8encode_(s.s+s.len, state.rune); + } return s; } -static Str fromenv_(Arena *a, const wchar_t *name) +static s8 fromenv_(arena *perm, c16 *name) { - // NOTE: maximum environment variable size is 2**15-1, so this - // cannot fail if the variable actually exists - static wchar_t w[1<<15]; - DWORD wlen = GetEnvironmentVariableW(name, w, sizeof(w)); + // Given no buffer, unset variables report as size 0, while empty + // variables report as size 1 for the null terminator. + i32 wlen = GetEnvironmentVariableW(name, 0, 0); if (!wlen) { - Str r = {0}; + s8 r = {0}; return r; } - return fromwide_(a, w, wlen); + + // Store temporarily at the beginning of the arena. + size cap = (perm->end - perm->beg) / (size)sizeof(c16); + if (wlen > cap) { + oom(); + } + s16 wvar = {0}; + wvar.s = (c16 *)perm->beg; + wvar.len = wlen - 1; + GetEnvironmentVariableW(name, wvar.s, wlen); + + byte *save = perm->beg; + perm->beg = (byte *)(wvar.s + wvar.len); + s8 var = fromwide_(perm, wvar); + perm->beg = save; + + return var; } -static Str installdir_(Arena *a) +static i32 truncsize(size len) { - wchar_t exe[MAX_PATH]; - Size len = GetModuleFileNameW(0, exe, MAX_PATH); - for (Size i = 0; i < len; i++) { - if (exe[i] == '\\') { - exe[i] = '/'; + i32 max = 0x7fffffff; + return len>max ? max : (i32)len; +} + +static s8 installdir_(arena *perm) +{ + byte *save = perm->beg; + + // GetModuleFileNameW does not communicate length. It only indicates + // success (buffer large enough) or failure (result truncated). To + // make matters worse, long paths have no fixed upper limit, though + // 64KiB is given as an approximate. To deal with this, offer the + // entire free region of the arena, far exceeding any path length. + // + // Computing sizes outside of the allocator isn't great, but the + // situation is constrained by this crummy API. + s16 exe = {0}; + exe.s = (c16 *)perm->beg; + i32 cap = truncsize(perm->end - perm->beg) / (i32)sizeof(c16); + exe.len = GetModuleFileNameW(0, exe.s, cap); + perm->beg = (byte *)(exe.s + exe.len); + + // Normalize by converting backslashes to slashes + for (size i = 0; i < exe.len; i++) { + if (exe.s[i] == '\\') { + exe.s[i] = '/'; } } - Str path = fromwide_(a, exe, len); + + s8 path = fromwide_(perm, exe); + perm->beg = save; // free the wide path return dirname(dirname(path)); } -static Str append2_(Arena *a, Str pre, Str suf) +static s8 append2_(arena *perm, s8 pre, s8 suf) { - Str s = newstr(a, pre.len+suf.len); - copy(copy(s, pre), suf); + s8 s = news8(perm, pre.len+suf.len); + s8copy(s8copy(s, pre), suf); return s; } -static Str makepath_(Arena *a, Str base, Str lib, Str share) +static s8 makepath_(arena *perm, s8 base, s8 lib, s8 share) { - Str delim = S(";"); - Size len = base.len + lib.len + delim.len + base.len + share.len; - Str s = newstr(a, len); - Str r = copy(s, base); - r = copy(r, lib); - r = copy(r, delim); - r = copy(r, base); - copy(r, share); + s8 delim = S(";"); + size len = base.len + lib.len + delim.len + base.len + share.len; + s8 s = news8(perm, len); + s8 r = s8copy(s, base); + r = s8copy(r, lib); + r = s8copy(r, delim); + r = s8copy(r, base); + s8copy(r, share); return s; } -static Str fromcstr_(char *z) +static s8 fromcstr_(u8 *z) { - Str s = {(Byte *)z, 0}; + s8 s = {0}; + s.s = z; if (s.s) { for (; s.s[s.len]; s.len++) {} } return s; } -ENTRYPOINT -int mainCRTStartup(void) +static config *newconfig_() +{ + arena perm = newarena_(1<<22); + config *conf = new(&perm, config, 1); + conf->perm = perm; + return conf; +} + +__attribute((force_align_arg_pointer)) +void mainCRTStartup(void) { - Config conf = {0}; - conf.delim = ';'; - conf.define_prefix = 1; - conf.arena = newarena_(); - Arena *a = &conf.arena; + config *conf = newconfig_(); + conf->delim = ';'; + conf->define_prefix = 1; + arena *perm = &conf->perm; - DWORD dummy; - HANDLE err = GetStdHandle(STD_ERROR_HANDLE); - error_is_console = GetConsoleMode(err, &dummy); + i32 dummy; + handles[1].handle = GetStdHandle(STD_OUTPUT_HANDLE); + handles[1].isconsole = GetConsoleMode(handles[1].handle, &dummy); + handles[2].handle = GetStdHandle(STD_ERROR_HANDLE); + handles[2].isconsole = GetConsoleMode(handles[2].handle, &dummy); - char **argv = (char **)allocarray(a, SIZEOF(*argv), CMDLINE_ARGV_MAX); - unsigned short *cmdline = (unsigned short *)GetCommandLineW(); - conf.nargs = cmdline_to_argv8(cmdline, argv) - 1; - conf.args = (Str *)allocarray(a, SIZEOF(Str), conf.nargs); - for (Size i = 0; i < conf.nargs; i++) { - conf.args[i] = fromcstr_(argv[i+1]); + u8 **argv = new(perm, u8 *, CMDLINE_ARGV_MAX); + c16 *cmdline = GetCommandLineW(); + conf->nargs = cmdline_to_argv8(cmdline, argv) - 1; + conf->args = new(perm, s8, conf->nargs); + for (size i = 0; i < conf->nargs; i++) { + conf->args[i] = fromcstr_(argv[i+1]); } - Str base = installdir_(a); - conf.envpath = fromenv_(a, L"PKG_CONFIG_PATH"); - conf.fixedpath = fromenv_(a, L"PKG_CONFIG_LIBDIR"); - if (!conf.fixedpath.s) { - Str lib = S(PKG_CONFIG_PREFIX "/lib/pkgconfig"); - Str share = S(PKG_CONFIG_PREFIX "/share/pkgconfig"); - conf.fixedpath = makepath_(a, base, lib, share); + s8 base = installdir_(perm); + conf->envpath = fromenv_(perm, L"PKG_CONFIG_PATH"); + conf->fixedpath = fromenv_(perm, L"PKG_CONFIG_LIBDIR"); + if (!conf->fixedpath.s) { + s8 lib = S(PKG_CONFIG_PREFIX "/lib/pkgconfig"); + s8 share = S(PKG_CONFIG_PREFIX "/share/pkgconfig"); + conf->fixedpath = makepath_(perm, base, lib, share); } - conf.top_builddir = fromenv_(a, L"PKG_CONFIG_TOP_BUILD_DIR"); - conf.sys_incpath = append2_(a, base, S(PKG_CONFIG_PREFIX "/include")); - conf.sys_libpath = append2_(a, base, S(PKG_CONFIG_PREFIX "/lib")); + conf->top_builddir = fromenv_(perm, L"PKG_CONFIG_TOP_BUILD_DIR"); + conf->sys_incpath = append2_(perm, base, S(PKG_CONFIG_PREFIX "/include")); + conf->sys_libpath = append2_(perm, base, S(PKG_CONFIG_PREFIX "/lib")); + conf->print_sysinc = fromenv_(perm, L"PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"); + conf->print_syslib = fromenv_(perm, L"PKG_CONFIG_ALLOW_SYSTEM_LIBS"); - appmain(conf); - ExitProcess(0); + uconfig(conf); + ExitProcess(handles[1].err || handles[2].err); + assert(0); } -static MapFileResult os_mapfile(Arena *a, Str path) +static filemap os_mapfile(arena *perm, s8 path) { - (void)a; - ASSERT(path.len > 0); - ASSERT(!path.s[path.len-1]); + assert(path.len > 0); + assert(!path.s[path.len-1]); - wchar_t wpath[MAX_PATH]; - int wlen = MultiByteToWideChar( - CP_UTF8, 0, (char *)path.s, path.len, wpath, MAX_PATH - ); - if (!wlen) { - MapFileResult r = {{0, 0}, MapFile_NOTFOUND}; - return r; - } + filemap r = {0}; - HANDLE h = CreateFileW( - wpath, - GENERIC_READ, - FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, - 0, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - 0 - ); - if (h == INVALID_HANDLE_VALUE) { - MapFileResult r = {{0, 0}, MapFile_NOTFOUND}; - return r; - } - - DWORD hi, lo = GetFileSize(h, &hi); - if (hi || lo>Size_MAX) { - CloseHandle(h); - MapFileResult r = {{0, 0}, MapFile_READERR}; - return r; - } else if (!lo) { - CloseHandle(h); - // Cannot map an empty file, so use the arena for a zero-size - // allocation, distinguishing it from a null string. - MapFileResult r = {newstr(a, 0), MapFile_OK}; - return r; + i32 handle = 0; + { + arena scratch = *perm; + s16 wpath = towide_(&scratch, path); + handle = CreateFileW( + wpath.s, + GENERIC_READ, + FILE_SHARE_ALL, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0 + ); + if (handle == INVALID_HANDLE_VALUE) { + r.status = filemap_NOTFOUND; + return r; + } } - HANDLE map = CreateFileMappingA(h, 0, PAGE_READONLY, 0, lo, 0); - CloseHandle(h); - if (!map) { - MapFileResult r = {{0, 0}, MapFile_READERR}; - return r; + r.data.s = (u8 *)perm->beg; + size cap = perm->end - perm->beg; + while (r.data.len < cap) { + i32 len = truncsize(cap - r.data.len); + ReadFile(handle, r.data.s+r.data.len, len, &len, 0); + if (len < 1) { + break; + } + r.data.len += len; } + CloseHandle(handle); - void *p = MapViewOfFile(map, FILE_MAP_READ, 0, 0, lo); - CloseHandle(map); - if (!p) { - MapFileResult r = {{0, 0}, MapFile_READERR}; + if (r.data.len == cap) { + // If it filled all available space, assume the file is too large. + r.status = filemap_READERR; return r; } - MapFileResult r = {{(Byte *)p, (Size)lo}, MapFile_OK}; + perm->beg += r.data.len; + r.status = filemap_OK; return r; } static void os_fail(void) { ExitProcess(1); + assert(0); } -static void os_write(int fd, Str s) +typedef struct { + c16 buf[1<<8]; + i32 len; + i32 handle; + b32 err; +} u16buf; + +static void flushconsole_(u16buf *b) { - ASSERT(fd==1 || fd==2); - DWORD id = fd==1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE; - HANDLE h = GetStdHandle(id); - DWORD n; + if (!b->err && b->len) { + i32 dummy; + b->err = !WriteConsoleW(b->handle, b->buf, b->len, &dummy, 0); + } + b->len = 0; +} - if (fd==2 && error_is_console) { - static wchar_t tmp[1<<12]; - int len = MultiByteToWideChar( - CP_UTF8, 0, (char *)s.s, s.len, tmp, sizeof(tmp) - ); - if (len) { - WriteConsoleW(h, tmp, len, &n, 0); - return; - } - // Too large, fallback to WriteFile +static void printc32_(u16buf *b, c32 rune) +{ + if (b->len > countof(b->buf)-2) { + flushconsole_(b); } + b->len += utf16encode_(b->buf+b->len, rune); +} - BOOL r = WriteFile(h, s.s, s.len, &n, 0); - if (!r || (Size)n!=s.len) { - os_fail(); +static void os_write(i32 fd, s8 s) +{ + assert((i32)s.len == s.len); // NOTE: assume it's not a huge buffer + assert(fd==1 || fd==2); + + b32 *err = &handles[fd].err; + if (*err) { + return; + } + + i32 handle = handles[fd].handle; + if (handles[fd].isconsole) { + // NOTE: There is a small chance that a multi-byte code point + // spans flushes from the application. With no decoder state + // tracked between os_write calls, this will mistranslate for + // console outputs. The application could avoid such flushes, + // which would require a distinct "close" flush before exits. + // + // Alternatively, the platform layer could detect truncated + // encodings and buffer up to 3 bytes between calls. This buffer + // would need to be flushed on exit by the platform. + // + // The primary use case for u-config is non-console outputs into + // a build system, which will not experience this issue. Console + // output is mainly for human-friendly debugging, so the risk is + // acceptable. + u16buf b = {0}; + b.handle = handle; + utf8 state = {0}; + state.tail = s; + while (state.tail.len) { + state = utf8decode_(state.tail); + printc32_(&b, state.rune); + } + flushconsole_(&b); + *err = b.err; + } else { + i32 dummy; + *err = !WriteFile(handle, s.s, (i32)s.len, &dummy, 0); } } diff --git a/.gnu-windows/src/profile b/.gnu-windows/src/profile new file mode 100644 index 0000000000..d82554a1b5 --- /dev/null +++ b/.gnu-windows/src/profile @@ -0,0 +1,6 @@ +# GNU Autotools "configure" detects w64devkit as an MSYS2 environment, so +# set some environment variables to disabuse it. Goes hand-in-hand with +# the busybox-w32 ash patch for libtool. +export PATH_SEPARATOR=';' +export ac_executable_extensions=.exe +export build_alias=ARCH diff --git a/.gnu-windows/src/variant-fortran.patch b/.gnu-windows/src/variant-fortran.patch deleted file mode 100644 index 55f4dfa53b..0000000000 --- a/.gnu-windows/src/variant-fortran.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/Dockerfile -+++ b/Dockerfile -@@ -102,3 +102,3 @@ RUN cat $PREFIX/src/gcc-*.patch | patch -d/gcc-$GCC_VERSION -p1 \ - --with-pic \ -- --enable-languages=c,c++ \ -+ --enable-languages=c,c++,fortran \ - --enable-libgomp \ -@@ -254,3 +254,3 @@ RUN sed -i 's#=/mingw/include#=/include#' /gcc-$GCC_VERSION/gcc/config.gcc \ - --with-mpfr-lib=/deps/lib \ -- --enable-languages=c,c++ \ -+ --enable-languages=c,c++,fortran \ - --enable-libgomp \ -@@ -288,3 +288,3 @@ RUN $ARCH-gcc -DEXE=gcc.exe -DCMD=cc \ - gcc gcc-ar gcc-nm gcc-ranlib gcov gcov-dump gcov-tool ld nm objcopy \ -- objdump ranlib readelf size strings strip windmc windres \ -+ objdump ranlib readelf size strings strip windmc windres gfortran \ - | xargs -I{} -P$(nproc) \ diff --git a/.gnu-windows/src/variant-i686.patch b/.gnu-windows/src/variant-x86.patch similarity index 56% rename from .gnu-windows/src/variant-i686.patch rename to .gnu-windows/src/variant-x86.patch index 530758e294..6cf1afad4b 100644 --- a/.gnu-windows/src/variant-i686.patch +++ b/.gnu-windows/src/variant-x86.patch @@ -1,43 +1,46 @@ --- a/Dockerfile +++ b/Dockerfile -@@ -65,3 +65,3 @@ COPY gnu-windows.c ../gnu-windows.ico \ +@@ -62,3 +62,3 @@ -ARG ARCH=x86_64-w64-mingw32 +ARG ARCH=i686-w64-mingw32 -@@ -93,2 +93,3 @@ RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure \ +@@ -90,2 +90,3 @@ --with-default-msvcrt=msvcrt-os \ + --with-default-win32-winnt=0x0501 \ && make -j$(nproc) \ -@@ -99,2 +100,5 @@ RUN ln -s $ARCH mingw - -+# Disable UTF-8 manifest for Windows XP (#58) -+RUN echo >/gcc-$GCC_VERSION/gcc/config/i386/winnt-utf8.manifest -+ - WORKDIR /x-gcc -@@ -103,2 +107,3 @@ RUN /gcc-$GCC_VERSION/configure \ +@@ -102,2 +103,3 @@ --with-sysroot=/bootstrap \ + --with-arch=pentium4 \ --target=$ARCH \ -@@ -132,4 +137,4 @@ RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ +@@ -138,4 +140,4 @@ --disable-dependency-tracking \ - --disable-lib32 \ - --enable-lib64 \ + --enable-lib32 \ + --disable-lib64 \ CFLAGS="-Os" \ -@@ -217,2 +222,3 @@ RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-headers/configure \ +@@ -223,2 +225,3 @@ --with-default-msvcrt=msvcrt-os \ + --with-default-win32-winnt=0x0501 \ && make -j$(nproc) \ -@@ -227,4 +233,4 @@ RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \ +@@ -233,4 +236,4 @@ --disable-dependency-tracking \ - --disable-lib32 \ - --enable-lib64 \ + --enable-lib32 \ + --disable-lib64 \ CFLAGS="-Os" \ -@@ -251,2 +257,3 @@ RUN /gcc-$GCC_VERSION/configure \ +@@ -257,2 +260,3 @@ --with-native-system-header-dir=/include \ + --with-arch=pentium4 \ --target=$ARCH \ +@@ -277,2 +281,3 @@ + --disable-win32-registry \ ++ --disable-win32-utf8-manifest \ + --enable-mingw-wildcard \ +@@ -384,3 +389,3 @@ + RUN cat $PREFIX/src/busybox-*.patch | patch -p1 \ +- && make mingw64u_defconfig \ ++ && make mingw64_defconfig \ + && sed -ri 's/^(CONFIG_AR)=y/\1=n/' .config \ diff --git a/7z b/7z new file mode 160000 index 0000000000..5d5fd260e9 --- /dev/null +++ b/7z @@ -0,0 +1 @@ +Subproject commit 5d5fd260e97971dba5f30440b493406f2fb7956e diff --git a/binutils b/binutils new file mode 160000 index 0000000000..218f8f3a61 --- /dev/null +++ b/binutils @@ -0,0 +1 @@ +Subproject commit 218f8f3a61eaf7387ed803504a7993b31776e1a3 diff --git a/busybox-w32 b/busybox-w32 new file mode 160000 index 0000000000..0ed1aea532 --- /dev/null +++ b/busybox-w32 @@ -0,0 +1 @@ +Subproject commit 0ed1aea532ce9b523e32c0e7841bcaea986a0371 diff --git a/cppcheck b/cppcheck new file mode 160000 index 0000000000..6813edd692 --- /dev/null +++ b/cppcheck @@ -0,0 +1 @@ +Subproject commit 6813edd69248ec49b4706f195eb8830b75738ad2 diff --git a/ctags b/ctags new file mode 160000 index 0000000000..6a12b33aaf --- /dev/null +++ b/ctags @@ -0,0 +1 @@ +Subproject commit 6a12b33aaf089b3dea5095117ae673e8d8b94e75 diff --git a/dockerfile b/dockerfile index e0299dface..c1ba923d36 100644 --- a/dockerfile +++ b/dockerfile @@ -1,4 +1,4 @@ -# Use an official Ubuntu image as base +# Use an official Debian image as base FROM debian:latest # Install git and any other dependencies needed for your build script @@ -15,4 +15,4 @@ RUN apt update && \ libx265-199 libxau6 libxcb1 libxdmcp6 libxml2 libxpm4 libyuv0 linux-libc-dev manpages-dev patch \ publicsuffix rpcsvc-proto unzip autoconf automake bison build-essential cmake curl dpkg-dev flex \ g++ gcc libfl-dev libgmp-dev libmpc-dev libmpfr-dev m4 make wget zip texinfo pkg-config python3 \ - python3-pip python3-venv file + python3-pip python3-venv python3-build file p7zip-full zstd tar mingw-w64 libtool \ No newline at end of file diff --git a/expat b/expat new file mode 160000 index 0000000000..c2d123f19e --- /dev/null +++ b/expat @@ -0,0 +1 @@ +Subproject commit c2d123f19e613c391d70accb2d371bbce332e62e diff --git a/gcc b/gcc new file mode 160000 index 0000000000..abcb615be0 --- /dev/null +++ b/gcc @@ -0,0 +1 @@ +Subproject commit abcb615be03807e2289d41cf382eee52c7d8ff07 diff --git a/gdb b/gdb new file mode 160000 index 0000000000..209e4a336e --- /dev/null +++ b/gdb @@ -0,0 +1 @@ +Subproject commit 209e4a336eea6b906eb967e496bfa16c85a150a2 diff --git a/gmp b/gmp new file mode 160000 index 0000000000..aa8ab164cb --- /dev/null +++ b/gmp @@ -0,0 +1 @@ +Subproject commit aa8ab164cb050aeaec81f16c4b0183351f7c71f9 diff --git a/libiconv b/libiconv new file mode 160000 index 0000000000..d326357280 --- /dev/null +++ b/libiconv @@ -0,0 +1 @@ +Subproject commit d326357280adf935726abaae2189a26ac64c9cb0 diff --git a/make b/make new file mode 160000 index 0000000000..1a8f02951a --- /dev/null +++ b/make @@ -0,0 +1 @@ +Subproject commit 1a8f02951acafb2d4c7ea5fb764ee82d74b3481b diff --git a/mingw-w64 b/mingw-w64 new file mode 160000 index 0000000000..287ef494fe --- /dev/null +++ b/mingw-w64 @@ -0,0 +1 @@ +Subproject commit 287ef494fe79da7862d2ad936e05e3f8a7b9f920 diff --git a/mpc b/mpc new file mode 160000 index 0000000000..4c90e51e99 --- /dev/null +++ b/mpc @@ -0,0 +1 @@ +Subproject commit 4c90e51e992ce8943e9c2500036cf5e93bde5355 diff --git a/mpfr b/mpfr new file mode 160000 index 0000000000..a52285994d --- /dev/null +++ b/mpfr @@ -0,0 +1 @@ +Subproject commit a52285994d300e8c53cf81b6046b4f6b5d17c331 diff --git a/nasm b/nasm new file mode 160000 index 0000000000..85b4eb6abc --- /dev/null +++ b/nasm @@ -0,0 +1 @@ +Subproject commit 85b4eb6abc59284319d0ac72c659d6a05f83e25f diff --git a/pdcurses b/pdcurses new file mode 160000 index 0000000000..e70146eca8 --- /dev/null +++ b/pdcurses @@ -0,0 +1 @@ +Subproject commit e70146eca863e34f750e1d08bf8cf9a01572299b diff --git a/vim b/vim new file mode 160000 index 0000000000..8599c27d2b --- /dev/null +++ b/vim @@ -0,0 +1 @@ +Subproject commit 8599c27d2b0a72a661a4e195e295362fdc265c25