Automated script to compile Wine 2.9 from scratch

Posted

28 May 2017

Updated

7 Jun 2017

Following up on my posts, Compiling Wine 1.9 from scratch on macOS with Retina mode and Creating a wine.app bundle manually, here's my script to automatically download and compile pre-requisite libraries, then download and compile Wine 2.9 32-bit, build the .app bundle, and even include a registry tweak to enable Retina Mode.

If you don't know Wine is - you shouldn't even read further! Go visit WineHQ: "Wine (originally an acronym for "Wine Is Not an Emulator") is a compatibility layer capable of running Windows applications... Wine translates Windows API calls into POSIX calls on-the-fly"

Let's start with a disclaimer - I compiled Wine from scratch only to suit my needs:

  • I wanted to run MS Office 2007 32-bit (since I had licenses),
  • I don't have Homebrew and don't like that it "installs" files everywhere on my system, and
  • I don't like the official Wine build for macOS which requires XQuartz (X11) to be installed first (which is really not required).

For most users, I'd suggest using the official Wine build, or if you need a supported version of Wine, buy CrossOver Mac by CodeWeavers. There are also various derivatives like Wineskin and WineBottler but many are no longer actively updated.

Folder Setup

First, create a new directory wine, and within that, create a subdirectory custom and usr:

mkdir wine
cd wine
mkdir custom
mkdir usr

My build wine script will only use the "wine" folder:

  • to download and extract libraries into numerous default sub-directories.
  • and compile binaries to usr (with appropriate sub-directories lib, bin, etc.) - all compiled files are only within this folder and not "installed" anywhere else on your Mac!

To build (compile) from source code, you'll need to have Xcode and more specifically, the Xcode command line developer tools installed (free). If not, you'll get the message note: no developer tools were found at '/Applications/Xcode.app', requesting install. Choose an option in the dialog to download the command line developer tools followed by a dialog box:

The

Start Wine script

A script called startwine is used to start the Wine File Manager (winefile) from an app bundle i.e. Win29.app. I chose winefile instead of the Wine Explorer or command prompt (wineconsole) since I found it worked best for me.

#!/bin/bash
path=$(dirname $0)
[[ "$path" == *"/"* ]] && cd "$path"
[[ "$PWD" == *"/MacOS" ]] && cd "../Resources/bin"
[[ "$PATH" != *"$PWD"* ]] && export PATH="$PWD":$PATH
unset WINEPREFIX

### Find folders called "drive_c", which are assumed to be Wine prefixes and prompt selection
choose_applescript() {
 prefixes="\"~/.Wine\""
 while IFS= read -r line; do
  prefixes=$prefixes",\"$(dirname "$line")\""
 done < <( mdfind drive_c kind:folder )
 choice=$(osascript -e "return choose from list { $prefixes } with prompt \"Select Wine folder\" with title \"Start Wine\" default items {\"~/.Wine\"}")
 [[ $choice == "false" ]] && exit 0
 [[ $choice != "~/.Wine" ]] && export WINEPREFIX=$choice
}
## Forcefully kill wine
kill_wine() {
 wineserver -k
 /bin/sleep 5
 tasks=""
 while read -r line; do 
  tasks="$tasks $line"
 done < <(ps -e | grep -e C: -e wineserver | cut -d' ' -f2)
 [[ -z "$tasks" ]] && echo kill $tasks
}
if [[ $(mdfind -count drive_c kind:folder) -gt 0 ]]; then
 choose_applescript
fi
## Start the Wine File Manager at the C drive
exec winefile C:\\

Note that the script does not have the .sh prefix, and should have executable permissions set:

chmod +x startwine

If the script finds more than one Wine Prefix (I'll explain what this is a bit later, but in short, it's the virtual C: drive for "Windows"), then it prompts the user to select one, using AppleScript. Ingenious but not very pretty - anyway, AppleScript can't be run directly.

AppleScript files (.scpt or .applescript) are always opened, never run by default. However, a file marked as executable (+x), without any extension, which has the first line #!/bin/bash will be run by default. Read more on Stack Overflow.

The script also attempts to kill wine when the Wine File Manager closes. In my experience, Wine starts various background processes as needed, and sometimes these background processes or WineServer itself, does not shutdown gracefully even though all visible windows have been closed.

Next, you'll need an icon file called wine.icns. You can download mine, which I created based on the original wine_logo.svg.

Build Wine script

In the directory "wine", create a file buildwine.sh:

#!/bin/bash
target="$PWD"
export PATH="$target/usr/bin:$PATH"
export CPPFLAGS="-I$target/usr/include"
export CFLAGS="-O2 -arch i386 -m32 -I$target/usr/include"
export CXXFLAGS="$CFLAGS "
export LDFLAGS=" -arch i386 -L$target/usr/lib"
export PKG_CONFIG_PATH="$target/usr/lib/pkgconfig"

## Download (unless file already exists), extract, configure and build
##  $1  target folder to run configure (as defined in the download file)
##  $2  download location
##  $3+ optional configure parameters (--prefix automatically set) 
download_build() {
 file=$(basename "$2") 
 [[ ! -f $file ]] && curl -L $2 -o "$file"
 [[ ! -d $1 ]] && tar xf "$file"
 pushd "$1" > /dev/null
 ./configure --prefix="$target/usr" ${@:3}
 make uninstall
 make -j5 install
 make install
 popd > /dev/null
}
## Relink executable files, with very specific "bin/" filter for samba 3 (samba 4 is impossible to fix)
##  $1  folder containing executable files
##  $2  optional file name filter
relink_bin() {
 pushd "$1" > /dev/null
 for file in "$2"*; do
  if [[ ! -L "$file" && "$file" != *".old" && $(file "$file") == *"Mach-O"* ]]; then
   echo Relink $file
   while read -r full; do 
    dir=$(dirname "$full")
    name=$(basename "$full") 
    [[ "$full" == "$target/usr/lib/$name" ]] && install_name_tool -change "$full" "@loader_path/../lib/$name" "$file"
    [[ "$full" == "bin/$name" ]] && install_name_tool -change "$full" "@loader_path/../lib/$name" "$file"
   done < <(otool -L "$file" | tail -n +2 | cut -d'(' -f1)
   fi
 done
 popd > /dev/null
}
## Relink dynamic library files, slightly different to replace id's
##  $1  folder containing library files
##  $2  optional file name filter
relink_lib() {
 pushd "$1" > /dev/null
 for file in "$2"*; do
  if [[ ! -L "$file" && "$file" != *".old" && $(file "$file") == *"Mach-O"* ]]; then
   while read -r full; do 
    dir=$(dirname "$full")
    name=$(basename "$full") 
    [[ "$full" == *"/$file" ]] && install_name_tool -id "$name" "$file"
    [[ "$full" != *"/$file" && "$full" == "$target/usr/lib/$name" ]] && install_name_tool -change "$full" "@loader_path/../lib/$name" "$file"
    [[ "$full" == "bin/$name" ]] && install_name_tool -change "$full" "@loader_path/../lib/$name" "$file"
   done < <(otool -L "$file" | tail -n +2 | cut -d'(' -f1)
   fi
 done
 popd > /dev/null
}
## List links within an executable or dynamic library
##  $1  target folder
##  $2  optional file name filter
list_links() {
 pushd "$1" > /dev/null
 for file in "$2"*; do
  if [[ ! -L "$file" && "$file" != *".old" && $(file "$file") == *"Mach-O"* ]]; then
   otool -L "$file"
  fi
 done
 popd > /dev/null
}
## Update last section of default wine.inf (registry) for retina mode
##  Lazy, assumptions. 3rd param typically 0 (string) or 0x00010001 (dword)
##  $1  location where share/wine/wine.inf resides
retina_wineinf() {
 echo 'HKCU,Software\Wine\Mac Driver,"RetinaMode",,"y"
HKCU,Control Panel\Desktop,"FontSmoothing",,"2"
HKCU,Control Panel\Desktop,"FontSmoothingOrientation",0x00010001,0x00000001
HKCU,Control Panel\Desktop,"FontSmoothingType",0x00010001,0x00000002
HKCU,Control Panel\Desktop,"FontSmoothingGamma",0x00010001,0x00000578
HKLM,System\CurrentControlSet\Hardware Profiles\Current\Software\Fonts,"LogPixels",0x00010001,0x000000cc
' >> "$1/share/wine/wine.inf"
}
## Create wine.app with 2 mandatory files, 2 custom files and other Resources
##  $1  name and location of wine.app
##  $2  location of 2 custom files "startwine" and "wine.icns"
create_app() {
 mkdir -p $1/Contents/MacOS
 mkdir -p $1/Contents/Resources
 pushd "$1/Contents" > /dev/null
 echo "APPL????" > PkgInfo
 echo '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleExecutable</key><string>startwine</string>
<key>CFBundleIconFile</key><string>wine.icns</string>
<key>CFBundleIdentifier</key><string>com.myByways.wine</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleVersion</key><string>1.9.21</string>
<key>CFBundleSupportedPlatforms</key><array><string>MacOSX</string></array>
<key>LSApplicationCategoryType</key><string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key><string>10.6</string>
</dict></plist>' > Info.plist
 cd Resources
 cp -R "$target/usr/bin" .
 cp -R "$target/usr/lib" .
 mkdir share
 cp -R "$target/usr/share/wine" share
 rmdir lib/gpext lib/nss_info lib/pdb lib/perfcount 
 cp "$target/custom/startwine" ../MacOS
 cp "$target/custom/wine.icns" .
 popd > /dev/null
}
## Create wine.app with 2 mandatory files, 2 custom files and other Resources, and then relink bins and libs
##  $1  name and location of wine.app
##  $2  location of 2 custom files "startwine" and "wine.icns"
create_app_relink() {
 create_app "$1" "$2"
 relink_bin "$1/Contents/Resources/bin"
 relink_lib "$1/Contents/Resources/lib"
}

## Mandatory libraries
## Sources: http://libpng.org http://freetype.org http://ijg.org
download_build libpng-1.6.29 http://downloads.sourceforge.net/project/libpng/libpng16/1.6.29/libpng-1.6.29.tar.gz --disable-static
download_build freetype-2.7.1 http://download.savannah.gnu.org/releases/freetype/freetype-2.7.1.tar.gz --disable-static
download_build jpeg-9b http://ijg.org/files/jpegsrc.v9b.tar.gz --disable-static

## Optional libraries
## Sources: http://libtiff.org http://www.littlecms.com http://www.unixodbc.org
download_build tiff-3.8.2 http://dl.maptools.org/dl/libtiff/tiff-3.8.2.tar.gz --disable-static
download_build lcms2-2.8 http://freefr.dl.sourceforge.net/project/lcms/lcms/2.8/lcms2-2.8.tar.gz --disable-static
download_build unixODBC-2.3.4 http://www.unixodbc.org/unixODBC-2.3.4.tar.gz --disable-static

## Mandatory samba (ntlm is required for Word authentication)
download_build samba-3.6.25/source3 https://ftp.samba.org/pub/samba/samba-3.6.25.tar.gz  --sbindir="$target/usr/bin" --disable-largefile --disable-swat --disable-smbtorture4 --disable-cups --disable-pie --disable-relro --disable-external-libtalloc --disable-external-libtdb --disable-fam --disable-dnssd --disable-avahi --without-dmapi --without-ldap --without-dnsupdate --without-pam --without-pam_smbpass --without-utmp --without-cluster-support --without-acl-support --without-sendfile-support --with-included-popt --with-included-iniparser --with-winbind 

## Build Wine
download_build wine-2.9 http://dl.winehq.org/wine/source/2.x/wine-2.9.tar.xz --enable-win16 --disable-win64 --without-x

## Create app and relink libraries
app="$target/wine29.app"
create_app_relink "$app" "$target/custom"

## Tweak to enable retina mode properly - comment out if not applicable!
retina_wineinf "$app/Contents/Resources"

Remove or comment the last line if you don't have a Retina Display

The script does too many things to describe, hopefully the comments will provide some clarity!

  • With nothing more than my judgement (read: best guess), I set various compliation options for the dependent 32-bit libraries and for Wine itself.
  • I couldn't figure out how to compile many other files, so the minimum set I decided on is:
  • I only bothered to figured out 32-bit compilation on 64-bit macOS, as I only wanted Wine to run 32-bit MS Office.

There is a guide to compile both 32- and 64-bit with a Shared WoW64, but that's beyond my capabilities.

Running the Build Wine script

Before you run it, set executable permission on buildwine.sh:

chmod +x buildwine.sh
./buildwine.sh

And now - drum roll please - run it!

The script will go and do its thing... and after 20-30 minutes, you should land up with a single Wine29.app bundle. That's all you need and all other files and folders can be removed.

I know there are some compilation warnings and missing dependencies, plus one error re-linking the profile executable (insufficient header space). I couldn't be bothered to fix it as I haven't encountered issues, but your mileage will vary. Specifically:

Relink profiles error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: changing install names or rpaths can't be redone for: profiles (for architecture i386) because larger updated load commands do not fit (the program must be relinked, and you may need to use -headerpad or -headerpad_max_install_names)

Running Wine

Run Wine29.app by double clicking or typing open wine29.app.

You'll need a virtual "C: drive" where you virtual "Windows" programs will be installed. Wine will only allow Windows .EXEs to access this drive (you should remove "shared" folders, to limit access to the macOS files). This virtual "C: drive" is stored in a folder, called a Wine Prefix.

If you don't have any Wine Prefixes, then Wine will create a default folder ~/.wine, download a couple of dependencies (Mono, etc.) and then, as defined in the startup script, start Wine's Explorer-like UI. From there, go ahead and run any Windows .EXE including any installers. I typically install Windows programs that I miss on macOS:

  • 7-zip - an open-source file archiver, used to compress and expand files using .7z or .ZIP formats.
  • Notepad++ - an open-source text editor with syntax highlighting and much more.

However, if you have multiple Wine Prefixes like I do, you'll be presented with a display like this - just pick one and off you go.

AppleScript dialog to select Wine Prefix when running StartWine script

Conclusion

Well, that's how I automatically keep up with the frequent Wine builds. About once every 2 weeks it seems - Wine just keeps getting better and better!

Generally, one shouldn't download any scripts from untrusted sources (this is should be in that category too) - but if you want, here are my scripts and icon file:

Build Wine Scripts and Icon buildwine_v0.1.zip
SHA-256 a50d638a755d140e18d2176e3a75574f33daaaced6c74aaeb00bcaf5abe4d0ea

Do validate the SHA-256 checksum.

Post script

Just as an aside: Wine 2.7 still rendered icons incorrectly on a Retina Mac (first image), but Wine 2.8+ fixes this (second image). I noticed this not just for the File Manager for for application icons as well.

Wine 2.7 with wrongly rendered icons in Retina ModeWine 2.9 with fixed rendering of icons in Retina Mode

Update 3 June: I really should've provided the SHA-256 checksum, seeing that I just posted about using checksums to validate downloads!

Update 7 June: For those who have not been following in the previous posts - you do need Xcode installed! Also clarified the Wine Prefix.