https://invisible-island.net/personal/
Copyright © 2018-2023,2024 by Thomas E. Dickey


Cross-compiling for MinGW

Overview

Cross-compiling can aid in development for programs where the target platform has poor tool support. Tool support has several aspects: cost, availability and the suitability of a tool for its intended use.

Background

Cross-compiling complements portability by enabling the use of development machines with better capabilities than a given target environment.

Portability

A portable program is one that has been made to work on multiple platforms:

The available platforms influenced the nature of the programs to be considered for porting. Rather than porting a massive application (or suite of programs), it was useful to port simple tools which did not use many system-dependent features.

For example, I ported both c_count (in 1990) and cproto (in 1993) to VMS with minimal change:

The MMS program was a problem, since it was an additional feature (not bundled with the C compiler) which not all VMS systems might have. The alternative (writing a brute-force build.com as I did for flist) was unsuitable for development. That problem was ultimately solved by MMK, Matthew Madison's clone of MMS in the mid-1990s (see current source).

In that regard, VMS was unusual. Build-environments on other platforms have provided a make program even when the tool developers would rather that their users use an IDE.

Non-Portable

Simple tools are easily ported. Not all useful programs are simple. Consider vile, lynx, ncurses. With more complicated programs, which used more system-dependent features, there were compelling reasons to make these programs fully exploit the systems on which they ran. The ports for vile, lynx, and ultimately ncurses, use native I/O. For example, Windows provides platform-specific functions which can be used to support these tools:

Other platforms (e.g., OS/2, VMS) provided their own platform-specific functions for native I/O.

For some time, using these features tied development into tools which ran only on the target platform.

Development

Early on, all of the ports which I worked on were developed on the target environment. During the 1980s, I worked on programs which I ported between different platforms, e.g.,

Later, during the 1990s, I continued in that way, with a mixture of programs which I created and those which others had begun.

vi-like-emacs

Vile development began in August 1990; I became involved in December 1992.

Vile uses separate files for terminal I/O drivers (and separate build scripts for different platforms). It also uses platform-dependent functions for handling file formats (e.g., VMS).

In addition to the *nix platforms with termcap/terminfo, curses and X11 drivers, these platforms have special I/O drivers:

lynx browser

Lynx development began shortly after my involvement with vile, though I did not become involved with Lynx until early 1997.

Lynx uses a simpler approach to displaying on the terminal (it usually uses just the curses API), but has platform-specific features for file I/O (VMS and MS-DOS pathnames) as well as networking features. Inevitably, there are platform-specific build-scripts.

According to comments in the source, the VMS port was begun by Lou Montulli in January 1993, using the CERN WWW library (developed 1990-1993). Foteos Macrides was active in that effort starting late in 1993. Macrides also made changes for Windows (console) ports in 1995, though others (Doug Kaufman and Wayne Buttles) appear to have done most of the development for the MS-DOS and Windows ports.

The changelog suggests that I began the OS/2 port in February 1998, though there was an earlier port by someone who did not provide source code (see mailing list from May 1997).

Either way, Lynx used the EMX (BSD-based) runtime libraries with OS/2 rather than native I/O.

MacRides stopped working on Lynx after releasing 2.7.2 at the end of 1997. I was able to continue work on the VMS port (using others' machines) until September 2008 (when the HP test-drive environment was no longer available).

ncurses

I did most of the initial work to port ncurses to various Unix systems, but for most of the non-Unix ports, others began those, and I finished them. The development environments for each of these were similar, using gcc on machines which had ample memory and diskspace:

There was no VMS port for ncurses. Other developers started this, but did not produce a workable port, as noted in the ncurses FAQ.

Each of the workable ports relied upon a set of POSIX utilities for running the build-scripts, and a low-cost compiler.

Other Programs

The larger programs (vile, lynx, ncurses) have many features. Some of those features can be improved by using platform-specific features. Smaller/simpler programs are more readily ported. For instance, these required little more than providing a suitable makefile and reimplementations of functions such as getopt:

Native-development

When I began developing C/C++ programs for MS-DOS and Windows 3.1 in 1993, I used TurboC++. The TurboC++ IDE was useful for debugging, but makefiles were the proper way to build vile (March 1993) and add (October 1993).

A little later (in 1995), developing on OS/2 Warp, I used the IBM C Set compiler. It had an IDE (see summary of IBM C Set 2.0 on WinWorld), but I did not find that useful.

DJGPP

Borland TurboC++ and IBM C Set cost money; vile and other programs which I develop outside my day job are distributed free of charge.

Paul Fox recommended djgpp, a port of gcc which ran on MS-DOS (using a memory extender), which cost nothing. After a while, I added that to the development tools which I used with MS-DOS.

Visual Studio

For building a program, just the compiler and a build-tool (and libraries) are needed.

When I began developing vile on MS-DOS in 1993, there were a few choices, ordered here from no-cost to several hundred dollars:

In paid work, a year or two later, I used Watcom (versions 10 and 11) and Visual C++ (beginning with version 4.0), but did not purchase a Visual C++ license until I began to develop winvile in 1997. That was for Visual C++ 5.0, which became obsolete a year later by the introduction of Visual C++ 6.0, and later by Visual Studio 2003 and 2005. I used those as well, letting others pay for licenses.

Today (writing this in 2019), a usable version of Visual Studio can be obtained free of charge, but until Visual Studio Express 2008 was released, there was no low-cost version of Visual Studio available.

WinVile

When developing for MS-DOS, I used the TurboC++ and Watcom IDEs in the early stages of a port, but relied on makefiles for most of the development work. The output from running make could be redirected to a file for analysis.

With different compilers, there was a problem: each of those compilers required a different set of environment variables to give the location of the compiler, the header files and the libraries. Compiling with DJGPP was simple. The DJGPP user's guide mentions just PATH and DJGPP. TurboC++ used three (PATH, INCLUDE, LIB). Watcom added to that list (see The WATCOM C/C++ Programmer's FAQ). Using more than one compiler on the same machine required an initialization script.

Developing with TurboC++ and Windows 3.1 did not work well because Windows 3.1 would stop while in TurboC++'s debugger. After Windows NT was available, I revisited this with a copy of Petzold's book and was able to get winvile working.

Visual C++ used even more environment variables than the preceding toolsets. It provided an initialization script (vcvars32.bat), to help. Calling the script was a nuisance because there was no way to run that automatically when starting the command-line interpreter cmd. There was a feature of cmd which looked as it it would solve that problem, but it did not work well enough:

After several years, Microsoft improved this behavior, but during the early 2000s, this only provided motivation to look for a better solution. Rather than start another console window, I decided to run winvile:

That console window was a problem. I found that it was usually (not always) possible to start a batch file via a shortcut “minimized” which would in turn start winvile. Since that did not always work (and killing the intermediate window would kill winvile), I looked for a different solution. I found one using the “Send To” feature of the Windows desktop:

For example, here is the initialization script vile.rc for Visual C++ 4:

source c:/vile/vile.rc
source vc4.vile

The first “source” command uses the normal initialization script, makiing it reusable across different compilers. Here (after trimming project-specific stuff) is /vile/vile.rc:

source vileinit.rc
set fk=auto
set modeline
set backup-style=off
set fc=78
set swap
set cs=black
set printing-high=255
set autocolor=50
set vis=rev
set recent-files=20
set recent-folders=20
 
bind-key execute-hypertext-command ^X-z
bind-key show-hypertext-command ^X-Z
 
~with set-extra-colors
        modeline reverse+green
        string underline+magenta
        regex magenta
        number cyan
        hypertext underline+red
        enum green
        isearch reverse+blue
        warning reverse+bold+red
        linenumber cyan+reverse
~endwith
 
store-procedure wingrep i="Row" i="Col"
        ~local %col
        setv %col &sub $2 1
        $1 goto-line
        beginning-of-line
        ~if &ge %col 1
                %col forward-character-to-eol
        ~endif
~endm
 
set modeline

Here is the translated vcvars32.bat script vc4.vile:

store-procedure prepend s="Variable" s="Directory"
~local %name %value
setenv $1 &cat $2 &cat $pathlist-separator &env $1
~endm
 
; begin vcvars32.bat
;
; Root of Visual C++ installed files.
setv %MSDevDir='g:\MSDEVSTD'
 
; VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
~if &equ &env "OS" "Windows_NT"
        setv %VcOsDir=WINNT
~else
        setv %VcOsDir=WIN95
~endif
 
write-message 'Setting environment for using Microsoft Visual C++ tools.'
 
; Include cdrom files in environment.
setv %vcsource=''
~if &not &equ %vcsource ''
        ~if &or &equ &env "OS" "Windows_NT" \
                &equ &env "OS" ""
                prepend "PATH" &cat %vcsource &cat '\BIN\' %VcOsDir
                prepend "PATH" &cat %vcsource '\BIN'
        ~endif
        prepend "INCLUDE" &cat %vcsource '\MFC\INCLUDE'
        prepend "INCLUDE" &cat %vcsource '\INCLUDE'
        prepend "LIB" &cat %vcsource '\MFC\LIB'
        prepend "LIB" &cat %vcsource '\LIB'
~endif
 
~if &or &equ &env "OS" "Windows_NT" \
        &equ &env "OS" ""
        prepend "PATH" &cat %MSDevDir &cat '\BIN\' %VcOsDir
        prepend "PATH" &cat %MSDevDir '\BIN'
~endif
prepend "INCLUDE" &cat %MSDevDir '\MFC\INCLUDE'
prepend "INCLUDE" &cat %MSDevDir '\INCLUDE'
prepend "LIB" &cat %MSDevDir '\MFC\LIB'
prepend "LIB" &cat %MSDevDir '\LIB'
; end vcvars32.bat

Initially, I did not save a copy of the original Microsoft script, but after a few years decided it would be useful because Microsoft was changing the way the script was used. Here is the original script for Visual C++ 6:

@echo off
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir=G:\PROGRA~1\MICROS~1\Common
 
rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir=G:\PROGRA~1\MICROS~1\Common\msdev98
 
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir=G:\PROGRA~1\MICROS~1\VC98
 
rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir=WIN95
if "%OS%" == "Windows_NT" set VcOsDir=WINNT
 
rem
echo Setting environment for using Microsoft Visual C++ tools.
rem
 
if "%OS%" == "Windows_NT" set PATH=%MSDevDir%\BIN;%MSVCDir%\BIN;%VSCommonDir%\TOOLS\%VcOsDir%;%VSCommonDir%\TOOLS;%PATH%
if "%OS%" == "" set PATH="%MSDevDir%\BIN";"%MSVCDir%\BIN";"%VSCommonDir%\TOOLS\%VcOsDir%";"%VSCommonDir%\TOOLS";"%windir%\SYSTEM";"%PATH%"
set INCLUDE=%MSVCDir%\ATL\INCLUDE;%MSVCDir%\INCLUDE;%MSVCDir%\MFC\INCLUDE;%INCLUDE%
set LIB=%MSVCDir%\LIB;%MSVCDir%\MFC\LIB;%LIB%
 
set VcOsDir=
set VSCommonDir=
 

In Visual C++ 7 (Visual Studio 2003), Microsoft renamed that to vsvars32.bat, but kept a vcvars32.bat (which called vsvars32.bat), for compatibility.

I translated vcvars32.bat for several versions (4, 4.1, 5, 6 and 7) before Microsoft's newer Visual Studio provided a far more complex script to address different architectures, e.g., 32-bit and 64-bit machines. Starting with Visual Studio Express 2008, I used the original approach, which (with Windows XP) largely solved the problem of the stray console window. Here is an example:

@echo off
setlocal
 
set PARTITION=vs2008x32
set TARGET_ARCH=i386
set TARGET_ARCH=x86
 
rem trim path to get rid of cygwin
set PATH=C:\Windows\system32;C:\Windows;c:\com
 
rem add PERL for vile's builds
set PATH=%PATH%;c:\perl\bin
 
call "c:\program files (x86)\microsoft visual studio 9.0\vc/vcvarsall.bat" %1
shift
run-winvile %*
endlocal

The second script, run-winvile.bat, handles differences in the winvile configuration:

@echo off
setlocal
set WINVILE_DIR="C:\Program Files (x86)\VI Like Emacs\bin"
cd %WINVILE_DIR%
IF ERRORLEVEL 1 goto :finish
set WINVILE_EXE=WinVile-wide-ole.exe
if exist %WINVILE_EXE% start %WINVILE_EXE% %1 %2 %3 %4 %5 %6 %7 %8 %9
:finish
endlocal

Those use nmake, which though it has become less popular, has the advantage that periodic updates for Visual Studio do not break the makefiles. That cannot be said of the Visual Studio project files, nor the “modern” VCBuild and MSBuild tools which have cropped up in the Visual Studio releases.

MinGW

Although it would seem to follow naturally after DJGPP, I did not consider developing with MinGW as an option until the mid-2000s. My email reminds me that other developers (initially for XFree86 and lynx) were involved in ports using MinGW as early as 1999. As the maintainer for Lynx, I integrated changes by a few others.

Other Compilers

Other compilers that I used on Windows include

That ignores cygwin and msys2; both are environments which can support tools for cross-compiling to use the Microsoft libraries.

Source-control

Because setting up development machines took substantial time, and porting programs involved several changes, I found it helpful to maintain source-control archives on the development machines. Doing that let me record changes as I made them, and use that information in updating the archives on my main machine.

During the 1990s, nothing appeared suitable for this purpose.

After 2000, things changed. I used ComponentSoftware's CS-RCS (from 2001-2005). This worked as an add-on to the Windows Explorer which executed RCS utilities that ran under a separate process (to allow making the front end add-on proprietary code as discussed in this thread). The reason for the separate process was to avoid entanglement with the dynamic linking issue.

Ultimately, I stopped using CS-RCS because it interfered with Windows Explorer when logged in as a user other than the one registered to use the product.

As a replacement, I used CVSNT in 2006, finding its local server awkward to use. At the same time, I tried TortoiseSVN and Perforce.

After a while, TortoiseGit was available. I use it to track updates to locally-built libraries (better than CS-RCS). Where CS-RCS was good (tracking changes to stray configuration files), Git is far less flexible.

Newer development machines have better networking support than the machines I used around 2000. I use WinSCP for mirroring development sources and logfiles to my file server.

Cross-development

Cross-compiling has the potential advantage of using tools and resources which may be more powerful than those on the target machine. It has the drawback that the cross-compiler itself may be less reliable, as well as that the libraries used in cross-compiling may not be up to date.

That said, the potential advantages seem to outweigh the drawbacks.

Building is more complicated. Cross-compiling involves more than just changing the name of the tool. In some programs, the configuration has to distinguish between the cross-compiler itself and tools which are built as part of the build. For example:

All of these use autoconf-based configure scripts. The table-generators are necessarily very simple and portable because the autoconf checks are not useful for those programs. That is because autoconf handles only one compile-environment. Table generators require two.

At the time (and perhaps still), the autoconf developers did not get involved in solving this problem. They were more concerned about terminology, as you can see by reading the documentation. As a result, the terminology which I use in my configuration scripts may differ (see CF_BUILD_CC for instance, which I began in 2004, from my work on ncurses beginning in 1998).

DJGPP

The DJGPP website has instructions for building a cross-compiler:

http://www.delorie.com/howto/djgpp/linux-x-djgpp-revised.html
Linux x86 Host Cross Compiler HOWTO for the DJGPP Target Revised
Charles Wilkins (November 14, 2002).

I followed those instructions and was able to set up a cross compiler for DJGPP in September 2003. In addition to the generic script cfg-djgpp

#!/bin/sh
# Id: cfg-djgpp,v 1.4 2010/05/22 17:32:43 tom Exp # configure to cross-compile for djgcc
#
# use: testing-9 (gcc 3.3)
# use: xen-freebsd (gcc 4.1.0)
#
TARGET=i586-pc-msdosdjgpp 
TOOLS=/usr/local/compiler/cross/djgpp
 
if test -d $TOOLS
then
        export PATH=$TOOLS:$PATH
fi
 
cfg-normal \
        --with-build-cc=gcc \
        --host=$TARGET \
        --target=$TARGET \
        $*

which I mentioned on the ncurses mailing list in 2006, I also created customized versions for lynx

#!/bin/sh
# configure lynx cross-compiled to djgpp
DJAPPS=/usr/local/djgpp
WTAPPS=$DJAPPS/contrib/watt32
export CPPFLAGS="-I$WTAPPS/inc -I$DJAPPS/include"
export LDFLAGS="-L$WTAPPS/lib -L$DJAPPS/lib"
cfg-djgpp \
  $*

and ncurses:

#!/bin/sh
# Id: cfg-djgpp-ncurses,v 1.2 2010/05/22 17:28:57 tom Exp 
cfg-djgpp \
  --without-normal        \
  --with-debug            \
  --with-fallbacks=vt100,ansi,cygwin,linux,djgpp,djgpp203,djgpp204 \
  --disable-database      \
  --enable-termcap        \
  --without-ada           \
  $*

MinGW

In 2005, I noticed some newsgroup comments about cross-compiling from Linux to MinGW, e.g., in discussion of aeditor:

   Newsgroups: comp.lang.ruby
   From: Tim Ferrell <Tim.Ferr...@mcgeecorp.com>
   Date: Sat, 5 Feb 2005 00:57:00 +0900
   Subject: Re: aeditor-2.3
   Reply | Reply to Author | Forward | Print | Individual Message | Show
   original | Report Abuse

   This link explains how to cross-compile for Windows from Linux...

   http://www.xraylith.wisc.edu/~khan/software/gnu-win32/mingw-cross-how.
   ..

   HTH,
   Tim

Later, in July 2008, Mark Robinson sent patches to allow vile and winvile to be built with cross-compiled MinGW. Since I was able to build both (with Visual Studio, and more capabilities), that was just an alternate way to build vi like emacs.

Ultimately what got my interest in cross-compiling for MinGW was Jürgen Pfeifer's patches to use MinGW for ncurses. He began developing those in September 2008, and I began integrating them into ncurses beginning in February 2009.

Earlier than this, I was certainly aware that ncurses did not work with MinGW (see mailing list comment from April 2006). But at that point I had thought that I might get ncurses to build and work in a Windows command window using Visual Studio C++. Because ncurses is built with shell scripts (and awk scripts) which create some of the source files, doing a Windows port in that way meant that I relied on generating those source-files on another system (the alternative, GnuWin32, simply could not handle those scripts).

Actually, native builds with MinGW were not as speedy or robust as I would have liked: Jürgen wanted to build 64-bit clients, which entailed adding a compiler from a different group, and modifying the MinGW configuration to work with it, as noted in README.MinGW:

Please also make sure that MSYS links to the correct directory containing
your MinGW toolchain. For TDM this is usually C:\MinGW64. In your Windows
CMD.EXE command shell go to the MSYS root directory (most probably
C:\MSYS or C:\MSYS\1.0) and verify, that there is a junction point mingw
that points to the MinGW toolchain directory. If not, delete the mingw
directory and use the mklink command (or the linkd.exe utility on older
Windows) to create the junction point.

Thereafter, if I wanted to reinstall MinGW, I would also have to reconstruct the configuration that supported the TDM compiler and get that to work again.

Although supporting Jürgen's changes for ncurses in MinGW got me involved with MinGW, it was vile (vi like emacs) which got me to experiment with cross-compilers which targeted MinGW:

Mentioning build-logs raises an issue: for several years I have collected logs from building my programs with the compilers available on each machine. Most of these build scripts use the same choices, so that adding the configuration to support cross-compiling vile would apply to other programs. I do not build everything all the time, though. I do these builds to check portability and correctness of whatever program I am currently working on. The ncurses build script is separate from the others because it builds and installs several configurations. I modified both scripts to attempt to build with MinGW in February 2012, to investigate the 64-bit cross compilers. My earlier builds were ad hoc, not as routine as they are now.

Simple programs...

I began cross-compiling with simple programs, i.e., command-line tools which do not require curses. My logs show the initial dates for these:

A few of those were not done earlier, because the native Windows ports seemed more useful:

I set those up using scripts to check for the availability of a cross-compiler, and if needed, a custom script for building the program. Because the packagers changed their configuration (and used different names on the different platforms), my script accounted for the different pathnames and filenames used for invoking the compiler.

Here is a script used for configuring with the 32-bit MinGW cross-compiler:

#!/bin/sh
# $Id: cfg-mingw,v 1.9 2018/01/04 00:19:10 tom Exp $
# configure to cross-compile using mingw32
 
BUILD_CC=${CC:-gcc}
unset CC
unset CXX
 
TARGET=`choose-mingw32 gcc pkg-config`
 
if test -n "$TARGET"
then
        PREFIX=
        test -d /usr/$TARGET && PREFIX="--prefix=/usr/$TARGET"
        cfg-normal \
                --with-build-cc=$BUILD_CC \
                --host=$TARGET \
                --target=$TARGET \
                $PREFIX "$@" || exit 1
else
        echo "? cannot find MinGW compiler in path"
        exit 1
fi

The script for choosing the compiler (finding it) is longer:

#!/bin/sh
# $Id: choose-mingw,v 1.1 2018/01/04 00:06:57 tom Exp $
# vile:sw=4 ts=4
#------------------------------------------------------------------------------
# Choose a mingw "target" for 32/64 bits.  This is especially for the Debian
# packages which provide old (gcc-mingw32) and new (gcc-mingw-w64)
# flavors, using symbolic links for more/less compatibility.
#
# Prefer the names which are not symlinked, but override that if more than
# one tool is specified.
 
$# = 0 ] && exec $0 gcc
 
save_IFS="$IFS"
IFS=':'
state=
target=
 
case "$0" in
*32)
        prefixes=\
i686-w64-mingw32:\
i686-pc-mingw32:\
i686-mingw32msvc:\
i586-mingw32msvc:\
mingw32
        ;;
*)
        prefixes=\
x86_64-w64-mingw32:\
amd64-mingw32msvc
        ;;
esac
 
for dir in $PATH
do
        for tool in $prefixes
        do
                check=
                for program in "$@"
                do
                        path=$dir/$tool-$program 
                        if [ ! -f "$path"  ]
                        then
                                [ -n "$check" ] && check="miss"
                                break
                        fi
                        [ -z "$check" ] && check="file"
                        [ -h "$path"  ] && check="link"
                        [ -t 2        ] || echo "$path" >&2
                done
                case "x$state" in
                x)
                        if [ -n "$check" ]
                        then
                                state=$check
                                target=$tool
                        fi
                        ;;
                xlink)
                        case "x$check" in
                        xfile)
                                state=$check
                                target=$tool
                                ;;
                        esac
                        ;;
                esac
                if [ ! -t 2 ]
                then
                        [ -n "$check" ] && printf '%s\t%s\t-> %s\n' "$state" "$check" "$target" >&2
                fi
        done
        [ -n "$target" ] && break
done
IFS="$save_IFS"
 
echo $target

ConVile and MinVile

The larger programs (ncurses, vile, lynx), are a bigger problem in cross-compiling. Targeting the command-line of MinGW (using MSYS, an early fork from Cygwin according to the MSYS2 History) looked interesting, but how to make vile work with its screen was unclear. The MinGW installer does provide development packages for its command-line, but none of those appear relevant:

All packages of MinGW Vim

However, MinGW provides a copy of Vim, which uses the termcap DLL which is not available using MinGW's installer:

DLL dependencies of MinGW Vim

Seeing that, I lost interest. Others did not (see this relic).

When I have changes to make in the native MinGW environment, I use ded to navigate in a Cygwin window, and run vile from ded. In other environments and platforms, this workaround is unnecessary.

Cross-compiling vile to work in a Windows console window was more straightforward. Mark Robinson had introduced a reduced configuration convile (for the console driver ntconio.c), and minvile (for the graphical Windows driver ntwinio.c) in July 2008. I adapted that for cross-compiling in March 2012.

Because I have several programs, I wrote utility scripts for the MinGW and package builds, called from custom scripts. That helps when setting up a build script for a given program. The custom script for vile is short:

#!/bin/sh
# $Id: mingwbuild-vile,v 1.2 2013/10/08 00:14:40 tom Exp $
# build vile for win32 using mingw32 cross-compiler
 
. setup-mingwbuild
 
CONFIG_PROG=convile
CONFIG_OPTS="--with-external-filters --with-screen=dos"
perform_build
 
CONFIG_PROG=convile
CONFIG_OPTS="--with-builtin-filters --with-screen=dos"
perform_build
 
CONFIG_PROG=minvile
CONFIG_OPTS="--with-builtin-filters --with-screen=windows"
perform_build

The utility script which I began in February 2012 (for 32-bit MinGW) is longer:

#!/bin/sh
# $Id: setup-mingwbuild,v 1.19 2019/01/18 00:44:51 tom Exp $
# vile:ts=4 sw=4
# Build-support for MinGW-32 cross-compiling.
#
# wrappers set these variables:
#       PROG - the generic program name
#       CONFIG_PROG - program name which is built
#       CONFIG_OPTS - configure options
 
BITS=32
MY_DESTDIR=BUILD-W$BITS
MY_LOGFILE=`partition`-mingw$BITS-run.log
MY_INSTALL=install
 
PKG_PREFIX=mingw$BITS-
PKG_QUERY="rpm -ql"
test -f /usr/bin/dpkg && PKG_QUERY="dpkg -L"
 
unset CDPATH
 
BUILD_DIR=`realpath .`
BUILD=$BUILD_DIR
 
failed() {
        echo "? $*"
        exit 1
}
 
. no-rcs
 
test_deb() {
        TEST_DEB=`dpkg -l |grep $PKG_PREFIX'.*'$2 |head -n 1|awk '{print $2; }' | sed -e "s/$PKG_PREFIX//"`
        eval $1="\$TEST_DEB"
        test -n "$TEST_DEB" && echo "** found package $2 -> $TEST_DEB"
}
 
# copy files from package
from_pkg() {
        echo "...from_pkg $*"
        case $# in
        0|1)
                failed "from_pkg package destdir file1 [file2 ...]"
                ;;
        *)
                ;;
        esac
 
        PACKAGE=$1
        shift 1
 
        TARGET=$1
        mkdir -p $TARGET
        shift 1
 
        while test $# != 0
        do
                WANTED=`basename $1`
                shift 1
                SOURCE=`$PKG_QUERY $PKG_PREFIX$PACKAGE 2>/dev/null | egrep "/$WANTED\$" | head -n 1`
                if test -n "$SOURCE"
                then
                        copy -v $SOURCE $TARGET/
                elif test -n "$BINARIES" && test -f $BINARIES/$WANTED
                then
                        copy -v $BINARIES/$WANTED $TARGET/
                else
                        failed "did not find $WANTED"
                fi
        done
}
 
# copy ".dll" file(s) from package
copy_dll() {
        echo "...copy_dll $*"
        case $# in
        0|1)
                failed "copy_dll package destdir [dlls]"
                ;;
        2)
                from_pkg $1 $2 `$PKG_QUERY $PKG_PREFIX$1 2>/dev/null | egrep '\.dll$'`
                ;;
        *)
                from_pkg $*
                ;;
        esac
}
 
# When this file is source'd clean out our target directories.
clean_destdir() {
        rm -rf $MY_DESTDIR
        mkdir -p $MY_DESTDIR
 
        test -d LOGS || mkdir LOGS
        rm -f LOGS/$MY_LOGFILE
}
 
clean_srcdir() {
        if test -f makefile || test -f Makefile
        then
                make distclean
        fi
        rm -f *.out
}
 
wrap_buildlog() {
cat <<EOF/
** 
`date`
** node: 
`hostname` (`partition`)
** user: 
`id`
EOF/

        echo "** BUILD $CONFIG_PROG $CONFIG_OPTS"
        case "x`partition`" in
        *freebsd*)
                NO_LOCAL=
                ;;
        *)
                NO_LOCAL=no-local
                ;;
        esac
        $NO_LOCAL cfg-mingw$BITS "$@" --prefix='' $CONFIG_OPTS || failed "configure"
        if test -f makefile || test -f Makefile
        then
                make
                make $MY_INSTALL DESTDIR=`realpath $MY_DESTDIR`
                clean_srcdir
        fi
}
 
# Build using mingwXX compiler installing into ./BUILD-WXX directory.
perform_build() {
        clean_srcdir
 
        rcs_save
 
        wrap_buildlog "$@" 2>&1 | tee $MY_LOGFILE
 
        if test -f $MY_LOGFILE
        then
                # split-up to avoid bogged-down buffering
                sed -e "s,$BUILD/$MY_DESTDIR,DESTDIR,g" \
                        -e "s,$BUILD,SOURCE,g" < $MY_LOGFILE >>LOGS/$MY_LOGFILE
                rm -f $MY_LOGFILE
        fi
}
 
cfg-mingw --help 2>/dev/null >/dev/null || failed "no MinGW-$BITS compiler found"
 
clean_destdir
 
# Caller can setup more than one CONFIG_xxxx, calling perform_build multiple
# times.

Cross-compiling vile produces workable executables. However, the available tools for creating installers (mainly NullSoft) were too primitive to be useful. I began creating scripts in 2005 for Inno Setup, using those in the native Windows development environment for the executables that I provide for others (see vile's downloads).

ncurses

I began cross-compiling ncurses in July 2012 (both 32-bit and 64-bit), using scripts to set the configure-options, e.g.,

#!/bin/sh
# $Id: mingwbuild-ncurses,v 1.1 2012/07/28 20:44:37 tom Exp $
# build for win32 using mingw32 cross-compiler
 
. setup-mingwbuild
 
CONFIG_PROG=ncurses32
CONFIG_OPTS="\
        --with-shared 
\
        --without-debug 
\
        --with-trace 
\
        --enable-term-driver 
\
        --enable-sp-funcs 
\
        --enable-widec 
\
        --with-fallbacks=unknown,rxvt 
\
        --with-tparm-arg=intptr_t 
\
        --without-ada"

perform_build

The --with-shared option tells the configure script to produce DLLs for this environment. The native MinGW tools did not support the options which I had found for creating DLLs. The cross-development tools did this.

Later (in October 2013), I created Debian package scripts for testing the DLLs when building my other programs. I provide the resulting DLLs in zip-files, for other developers. End users generally use packages built by others, as I mentioned here:

Use ncursesw6 on mingw

ncurses 6.0 was first packaged for MinGW in March 2016 by Erwin Waterlander:

[Mingw-users] [ANNOUNCEMENT] New: msys-ncurses 6.0
From: Erwin Waterlander <water...@xs...> - 2016-03-16 19:59:30

Hi,

A new package is available: msys-ncurses 6.0.

This package is not installable via mingw-get, but needs manual
installation. You can download the files from:

https://sourceforge.net/projects/mingw/files/MSYS/Contributed/ncurses/ncurses-6.0-2/

Only narrow text is supported (no wide text (Unicode)).

This library can be used for msys applications in the mintty terminal
emulator. msys-ncurses will not work in Windows consoles like Windows
Console, Console(2), ConsoleZ, or ConEmu.

To run msys in mintty type:
msys.bat -mintty MSYS

or for a mingw32 environment:
msys.bat -mintty MINGW32

regards,

--
Erwin Waterlander
http://waterlan.home.xs4all.nl/
Later, it was added to the MinGW installer's inventory. This is in the contributed section, like pdcurses:

MinGW installer with ncurses

The DLLs which I build run in Windows consoles, as outlined in Juergen's README.MinGW. However, Keith Marshall pointed out (in August 2020) that these use different options than he uses for building MinGW:

I'm looking for ncurses, to interoperate with a MinGW GCC-9.2.0 compiler
tool chain.  I first tried
https://invisible-mirror.net/archives/ncurses/win32/mingw32.zip, but
that is broken, because its libncursesw6.dll depends on libgcc_s_sjlj-1.dll

  $ mingw32-ldd ~/VirtualBox/share/libncursesw6.dll
  /home/keith/VirtualBox/share/libncursesw6.dll
   +- KERNEL32.dll
   +- msvcrt.dll
   +- USER32.dll
   +- libgcc_s_sjlj-1.dll

FYI, no legitimately published 32-bit MinGW compiler will introduce any
such dependency; any which does is illegitimately published, insofar as
it masquerades as a MinGW product, infringing the MinGW trademark[1].

Since this clearly isn't compatible with a real MinGW 32-bit compiler,
(which requires libgcc_s_dw2-1.dll), I downloaded the source from
https://invisible-mirror.net/archives/ncurses/ncurses-6.2.tar.gz,
...

That is a result of cross-compiling rather than native builds. On my Debian machine, for instance, I have only these:

$ locate libgcc |grep dll
/usr/lib/gcc/i686-w64-mingw32/8.3-posix/libgcc_s_sjlj-1.dll
/usr/lib/gcc/i686-w64-mingw32/8.3-win32/libgcc_s_sjlj-1.dll
/usr/lib/gcc/x86_64-w64-mingw32/8.3-posix/libgcc_s_seh-1.dll
/usr/lib/gcc/x86_64-w64-mingw32/8.3-win32/libgcc_s_seh-1.dll

However, I could build using Fedora:

$ locate libgcc | grep dll
/usr/i686-w64-mingw32/sys-root/mingw/bin/libgcc_s_dw2-1.dll
/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgcc_s_seh-1.dll

but generally use Debian because they provide the most bug reports.

lynx

Cross-compiling lynx is more challenging, because (unlike vile) it does not provide a Windows console display driver. It uses curses or (with some restrictions) slang.

To cross-compile an application which uses external libraries, you need the libraries (at least the import libraries) available while cross-compiling.

I began cross-compiling lynx in 2012, first trying Ubuntu 11.10 (February), then Debian 6 and Fedora 16 (August). Like my other programs, I use a build-all-XXX script for testing updates. I added this chunk to the script for MinGW64:

        if ( cfg-mingw --version 2>/dev/null >/dev/null )
        then
                mingwbuild-$PROG && run-makensis */*.nsi || rmdir BUILD-W32
                test "$HAVE_RPM" = yes && rpmbuild-mingw-$PROG
                test "$HAVE_DEB" = yes && debbuild-mingw-$PROG
        else
                echo "** MINGW-32 not found"
        fi
 
        if ( cfg-mingw64 --version 2>/dev/null >/dev/null )
        then
                mingw64build-$PROG && run-makensis */*.nsi || rmdir BUILD-W64
                test "$HAVE_RPM" = yes && rpmbuild-mingw64-$PROG
                test "$HAVE_DEB" = yes && debbuild-mingw64-$PROG
        else
                echo "** MINGW-64 not found"
        fi

Debian provides the tools, but very few libraries. Checking with aptitude, I see 6. Only libz-mingw-w64-dev is useful for lynx. If I want anything more in Debian, I will have to build it myself.

Fedora provides a package for PDCurses which can be used in cross-compiling to MinGW. It packages neither ncurses nor slang for cross-compiling. Both can run in a Windows console.

For the record, the packager's statement that “ncurses is not available for MinGW / Windows” dates from January 2009 — about a month before I began integrating Juergen's changes to port ncurses to MinGW. The port was stable sometime before I released ncurses 5.8 in 2011.

Library dependencies

Unlike the other programs, Lynx supports several configurations that depend upon external libraries.

Native

My native builds on Windows use more than one environment, with corresponding build-trees.

The last three are under the home-directory for my development account, in the corresponding environment. As in my Linux and Unix machines, each version of a program has its own directory. I use RCS on the development file-server for source control. I update these Windows environments using rsync and WinSCP.

I do test-compiles with all of the versions of Visual Studio which I am able to install. In 2019-2020, I revamped my development system for building Lynx. I used batch-files from WinVile to run “command-line” builds for each of Lynx's dependencies and collected those into a directory tree, which is a local Git repository.

Here is a screenshot of that directory tree:

Windows: Lynx libraries

The Visual Studio build-tree is more static; directory names do not change often. Here is a screenshot of that directory tree:

Windows: build-tree

I maintain the patches needed to build those libraries via WinSCP. In particular, slang requires patching because its developer has not provided a suitable source for more than ten years.

Besides the compression libraries (bzip2, gzip, zlib), Lynx uses OpenSSL libraries which I use as mentioned on my Lynx development page. I decided to not build my own gettext and iconv libraries. Some people do. For example:

Look closer, and notice that those have to be patched and maintained.

Cross-compiler

Most of the programs which I cross-compile to MinGW have few dependencies. “MinVile” and ncurses use nothing more than the DLLs which are either standard, or are part of the cross-compiler toolchain. Oddly enough, those toolchains do not include a tool to show dependencies, such as ldd.

For Windows development, these are useful, with a few caveats:

Caveats:

Still, there was no analogous cross-compiling tool in 2015, according to the answer given for Finding DLLs required of a Win exe on Linux (cross-compiled with mingw)?

A year later (October 2016), Keith Marshall wrote a script (mingw32-ldd.sh) which uses the native- or cross-compiler's objdump to analyze an executable (e.g., .exe or .dll) and show the DLLs which it references. The script works in both MinGW32 (native) and MinGW (cross-compiled) environments. For the latter, the user is expected to rename the script to use the same prefix as the other cross-compiling tools.

However, Debian and Fedora use different prefixes (and those have changed since 2012). I use scripts to choose the appropriate prefix for the MinGW32 and MinGW64 toolchain from lists which cover different platforms rather than make a lot of copies of Keith's script:

#!/bin/bash
# -----------------------------------------------------------------------------
# Copyright 2023 by Thomas E. Dickey
  ...
# -----------------------------------------------------------------------------
# This is a wrapper for Keith Marshall's "mingw32-ldd.sh" script, which along
# with my choose-mingw32 and choose-mingw64 scripts, automatically selects
# the proper target based on the output from "file".
mytemp=$(mktemp -d)
trap 'rm -rf "$mytemp"; exit 1' INT QUIT TERM HUP
trap 'rm -rf "$mytemp"; exit 0' EXIT
 
PATH=$mytemp:$PATH
export PATH
 
for name in "$@"
do
        [ -f "$name" ] || continue
        case $(file "$name") in
        *PE32*386*)
                target=$(choose-mingw32 2>/dev/null)
                ;;
        *PE32*x86-64*)
                target=$(choose-mingw64 2>/dev/null)
                ;;
        *)
                continue
                ;;
        esac
        script="$mytemp"/"$target"-ldd.sh
        if [ ! -f "$script" ]
        then
                echo "#!/bin/bash" > "$script"
                echo ". mingw32-ldd.sh" >> "$script"
                chmod +x "$script"
        fi
        $script "$name"
done
# vile:ts=4 sw=4

Here are listings for ncurses (built on Debian 10), with 32-bit:

$ mingw-ldd libncursesw6.dll
libncursesw6.dll
 +- KERNEL32.dll
 +- msvcrt.dll
 +- USER32.dll
 +- libgcc_s_sjlj-1.dll

and 64-bit:

$ mingw-ldd libncursesw6.dll
libncursesw6.dll
 +- KERNEL32.dll
 +- msvcrt.dll
 +- USER32.dll

With Debian 11, the 32-bit dependencies changed, as noted in debian-devel-changes:

gcc-mingw-w64 (24) unstable; urgency=medium

  * This release of gcc-mingw-w64 changes the 32-bit exception handling
    mechanism from SJLJ to Dwarf2, to match MSYS2, Fedora, and other
    toolchains, and to allow the Rust toolchain to provide 32-bit
    cross-compilers.
    This will require rebuilding all artifacts built with previous
    releases of the toolchain. Shared binaries will need to be shipped
    with the dw2 DLL instead of the sjlj DLL.

 -- Stephen Kitt <skitt@debian.org>  Sat, 21 Nov 2020 14:55:37 +0100

i.e.,

$ mingw-ldd libncursesw6.dll
libncursesw6.dll
 +- KERNEL32.dll
 +- msvcrt.dll
 +- USER32.dll
 +- libgcc_s_dw2-1.dll

but the 64-bit dependencies did not.

Having mingw-ldd can simplify the problem of providing the required DLLs when cross-compiling lynx. The setup-mingwbuild script has functions for copying DLLs from development packages. I used that in the mingwbuild-lynx script:

#!/bin/sh
# $Id: mingwbuild-lynx,v 1.14 2023/01/24 00:33:34 tom Exp $
# vile:ts=4 sw=4
# build for win32 using mingw32 cross-compiler
 
copy_binaries() {
        BINARIES=/usr/build/lynx/package-2.8.8dev.16-iss
 
        from_pkg bzip2       $MY_DESTDIR/bin/ bzip2.exe 
        copy -v $BINARIES/gzip.exe   $MY_DESTDIR/bin/
 
        copy_dll win-iconv   $MY_DESTDIR/bin/
        copy_dll gettext     $MY_DESTDIR/bin/
        # /usr/i686-w64-mingw32/sys-root/mingw/bin/libgcc_s_sjlj-1.dll
        copy_dll gcc         $MY_DESTDIR/bin/
 
        case "$CONFIG_OPTS" in
        *disable-idn*)
                ;;
        *)
                copy_dll libidn2     $MY_DESTDIR/bin/
                copy_dll libidn      $MY_DESTDIR/bin/
                ;;
        esac
 
        case "$CONFIG_OPTS" in
        *with-brotli*)
                copy_dll brotli      $MY_DESTDIR/bin/
                ;;
        esac
 
        case "$CONFIG_OPTS" in
        *with-bzlib*)
                copy_dll $PKG_BZLIB     $MY_DESTDIR/bin/
                ;;
        esac
 
        case "$CONFIG_OPTS" in
        *with-screen=pdcurses*)
                if test -f /usr/bin/dpkg
                then
                        copy_dll pdcurses3   $MY_DESTDIR/bin/
                else
                        copy_dll pdcurses    $MY_DESTDIR/bin/
                fi
                ;;
        esac
 
        case "$CONFIG_OPTS" in
        *with-ssl*)
                copy_dll openssl     $MY_DESTDIR/bin/
                ;;
        esac
 
        case "$CONFIG_OPTS" in
        *with-zlib*)
                copy_dll $PKG_ZLIB   $MY_DESTDIR/bin/
                ;;
        esac
}
 
. setup-mingwbuild
 
PKG_BZLIB=bzip2
PKG_ZLIB=zlib
 
CONFIG_PROG=lynx
CONFIG_OPTS=
case `partition` in
*fedora*)
        CONFIG_OPTS="$CONFIG_OPTS --with-ssl"
        CONFIG_OPTS="$CONFIG_OPTS --with-brotli"
        CONFIG_OPTS="$CONFIG_OPTS --with-bzlib"
        CONFIG_OPTS="$CONFIG_OPTS --with-zlib"
        CONFIG_OPTS="$CONFIG_OPTS --enable-gzip-help"
        CONFIG_OPTS="$CONFIG_OPTS --enable-htmlized-cfg"
        CONFIG_OPTS="$CONFIG_OPTS --with-screen=pdcurses"
        ;;
*)
        test_deb PKG_BZLIB libbz2
        if test -n "$PKG_BZLIB"
        then
                CONFIG_OPTS="$CONFIG_OPTS --with-bzlib"
        fi
        test_deb PKG_ZLIB libz
        if test -n "$PKG_ZLIB"
        then
                CONFIG_OPTS="$CONFIG_OPTS --with-zlib"
                CONFIG_OPTS="$CONFIG_OPTS --enable-gzip-help"
                CONFIG_OPTS="$CONFIG_OPTS --enable-htmlized-cfg"
        fi
        CONFIG_OPTS="$CONFIG_OPTS --with-screen=pdcurses"
        ;;
esac
MY_INSTALL=install-full
perform_build
copy_binaries 2>&1 | tee -a LOGS/$MY_LOGFILE

That case-statement is cumbersome.

Here is a listing for (32-bit) Lynx created on Fedora:

$ mingw-ldd lynx.exe
lynx.exe
 +- ADVAPI32.dll
 +- libbz2-1.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 +- libcrypto-3.dll
 |   +- ADVAPI32.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 |   +- api-ms-win-core-path-l1-1-0.dll
 |   +- USER32.dll
 |   +- WS2_32.dll
 |   +- zlib1.dll
 |   |   +- KERNEL32.dll
 |   |   +- msvcrt.dll
 |   +- libssp-0.dll
 +- iconv.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 |   +- libssp-0.dll
 +- libidn2-0.dll
 |   +- libgcc_s_dw2-1.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 |   +- libssp-0.dll
 +- KERNEL32.dll
 +- msvcrt.dll
 +- pdcurses.dll
 |   +- ADVAPI32.dll
 |   +- libgcc_s_dw2-1.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 |   +- USER32.dll
 +- libssl-3.dll
 |   +- libcrypto-3.dll
 |   |   +- ADVAPI32.dll
 |   |   +- KERNEL32.dll
 |   |   +- msvcrt.dll
 |   |   +- api-ms-win-core-path-l1-1-0.dll
 |   |   +- USER32.dll
 |   |   +- WS2_32.dll
 |   |   +- zlib1.dll
 |   |   |   +- KERNEL32.dll
 |   |   |   +- msvcrt.dll
 |   |   +- libssp-0.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 |   +- libssp-0.dll
 +- USER32.dll
 +- WS2_32.dll
 +- zlib1.dll
 |   +- KERNEL32.dll
 |   +- msvcrt.dll
 +- libssp-0.dll
  

Using the information from mingw-ldd, here is an example of the direct dependencies for Lynx with Fedora:

total 9436
-rwxr-xr-x. 1 tom users   36595 Jan 18 19:00 iconv.dll
-rwxr-xr-x. 1 tom users  112718 Jan 18 19:00 libbz2-1.dll
-rwxr-xr-x. 1 tom users 4132118 Jan 18 19:00 libcrypto-3.dll
-rwxr-xr-x. 1 tom users  410601 Jan 18 19:00 libidn2-0.dll
-rwxr-xr-x. 1 tom users  738809 Jan 18 19:00 libssl-3.dll
-rwxr-xr-x. 1 tom users  205137 Jan 18 19:00 pdcurses.dll
-rwxr-xr-x. 1 tom users  134385 Jan 18 19:00 zlib1.dll
-rwxr-xr-x. 1 tom users   78724 Jan 18 19:00 bzip2.exe
-r--r--r--. 1 tom users   68096 Oct 15  2007 gzip.exe
-rwxr-xr-x. 2 tom users 1861382 Feb 20 10:27 i686-w64-mingw32-lynx.exe
-rwxr-xr-x. 2 tom users 1861382 Feb 20 10:27 lynx.exe
  

However, some of those have further dependencies, and can be overlooked if the list is maintained manually. Here is a listing showing all of the dependencies:

ADVAPI32.dll ?
KERNEL32.dll ?
USER32.dll ?
WS2_32.dll ?
api-ms-win-core-path-l1-1-0.dll ?
iconv.dll
libbz2-1.dll
libcrypto-3.dll
/usr/i686-w64-mingw32/sys-root/mingw/bin/libgcc_s_dw2-1.dll
libidn2-0.dll
libssl-3.dll
/usr/i686-w64-mingw32/sys-root/mingw/bin/libssp-0.dll
msvcrt.dll ?
pdcurses.dll
zlib1.dll
bzip2.exe
gzip.exe
lynx.exe
  

The listing shows two types of extra dependencies: