# -*- mode: ruby -*-
# vi: set ft=ruby :
#
# Automated rebuild of bootloader on misc. platforms
#
# Copyright (C) 2016-2023 PyInstaller Development Team
#
# Based on a Vagrantfile by Thomas Waldmann. Thanks!
# Copyright (C) 2015 The Borg Collective http://borgbackup.readthedocs.org/


Vagrant.require_version ">= 1.9.5" # Enabling shared folders for winssh

# half-official Debian 8, incl. vboxfs kernel module for shared folders
DEBIAN = "debian/contrib-buster64"

if not (Dir.exist?('./src') and File.exist?('./wscript'))
   abort("vagrant must be called from within the 'bootloader' directory")
end

# Test if a variable is `true`, like a shell-variable: Unset (nil), empty
# string or some value expressing `false` means `false`.
def is_set?(s)
  not (s == nil or s == "" or (s =~ /^(false|f|n|no|0)$/i))
end

# Test if the environment variable `name` is set to `value` (case-insensitive)
def is_env?(name, value)
  ENV[name] and ENV[name].downcase == value.downcase
end


def packages_clang11_debianoid
  # Building the universal2 (x86_64/arm64) bootloader for macOS does
  # not seem to work with clang-7 that comes with Debian Buster. So
  # we need to install a recent version from apt.llvm.org.
  return <<-EOF
    # avoid any prompts, from http://askubuntu.com/questions/146921
    export DEBIAN_FRONTEND=noninteractive
    # needed for apt-add-repository
    apt-get install -y software-properties-common
    # setup llvm repository and install clang-11
    wget https://apt.llvm.org/llvm.sh
    chmod +x llvm.sh
    ./llvm.sh 11
    # symlink /usr/bin/clang-11 to /usr/bin/clang
    ln -s /usr/bin/clang-11 /usr/bin/clang
  EOF
end

def packages_debianoid
  # This always also installs the software for cross-building for OS X and
  # Windows. Differentiating here is most probably not worth the effort.
  return <<-EOF
    dpkg --add-architecture i386
    apt-get update
    # avoid any prompts, from http://askubuntu.com/questions/146921
    export DEBIAN_FRONTEND=noninteractive
    apt-get -y -o Dpkg::Options::="--force-confdef" \
               -o Dpkg::Options::="--force-confold" upgrade
    # prevent grub-pc update
    echo "grub-pc hold" | sudo dpkg --set-selections
    apt-get install -y python3 python3-dev python3-setuptools \
                       gcc libz-dev libz-dev:i386 \
                       gcc-i686-linux-gnu \
                       gcc-mingw-w64
  EOF
end

def packages_osxcross_debianoid
  # Install packages required for building osxcross
  return <<-EOF
    apt-get update
    # avoid any prompts, from http://askubuntu.com/questions/146921
    export DEBIAN_FRONTEND=noninteractive
    apt-get -y -o Dpkg::Options::="--force-confdef" \
               -o Dpkg::Options::="--force-confold" upgrade
    # prevent grub-pc update
    echo "grub-pc hold" | sudo dpkg --set-selections
    # these are typically installed already on a GNU/Linux system
    apt-get install -y cmake make sed kmod
    apt-get install -y git
    # requirements for osxcross SDK extraction script (not listed in tools/get_dependencies.sh)
    apt-get install -y libxml2-dev libicu-dev libssl-dev libbz2-dev liblzma-dev
  EOF
end

def prepare_osxcross_debianiod
  # Download osxcross source and install packages defined there
  return <<-EOF
    git clone --depth 1 https://github.com/tpoechtrager/osxcross.git
    # install requirements for cctools
    sudo osxcross/tools/get_dependencies.sh
  EOF
end

def build_osxcross
  return <<-EOF
    PATH=$PATH:/sbin:/usr/sbin
    SDK=/vagrant/bootloader/_sdks/osx
    cd ~vagrant/osxcross
    mkdir -p $SDK
    # Extract the SDK from Xcode.xip into MacOSX*.tar.*
    ./tools/gen_sdk_package_tools_dmg.sh $SDK/Xcode_tools.dmg || exit 1
    # Take the SDK with highest version
    mv $(ls MacOSX*.sdk.tar.* | sort -V | tail -n1) tarballs/
    # Build the build-tools into directory `target/`
    UNATTENDED=1 ./build.sh

    cd ~vagrant/osxcross/target
    echo "Minimizing the SDK to include only what is required for building"
    echo "PyInstaller"
    rm -rf libexec/as/{arm,ppc,ppc64} # platforms not supported by pyinstaller
    rm -rf SDK/tools # osxcross tools, not used for building the bootloader
    cd SDK/MacOSX*.sdk
    rm -rf usr/lib/{php,dtrace} usr/share usr/bin
    cd System/Library/
    mv Frameworks Frameworks.off
    mkdir Frameworks
    mv Frameworks.off/{ApplicationServices,CoreGraphics,ImageIO,Carbon,CoreServices,IOKit,CFNetwork,CoreText,IOSurface,CoreFoundation,DiskArbitration,Security,ColorSync}.framework Frameworks
    rm -rf Frameworks.off CoreServices Printers PrivateFrameworks
    cd ~vagrant/osxcross

    # Create a tar-ball including the SDK and the build-tools
    echo "Packaging SDK and cctools - this may take a while"
    tar -C target --xz -cf $SDK/osxcross.tar.xz .
  EOF
end


def update_darwin
  return <<-EOF
    set +x
    softwareupdate --ignore iTunesX
    softwareupdate --ignore iTunes
    softwareupdate --install --all
  EOF
end

def packages_darwin
  return <<-EOF
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    export HOMEBREW_NO_ANALYTICS=1
    brew analytics off
    brew update
    brew upgrade --all
    brew install pkg-config
    touch ~vagrant/.bash_profile ; chown vagrant ~vagrant/.bash_profile
  EOF
end

# Install required cygwin packages and configure environment
#
# Microsoft/EdgeOnWindows10 image has MLS-OpenSSH installed by default,
# which is based on cygwin x86_64 but should not be used together with cygwin.
# In order to have have cygwin compatible bash 'ImagePath' is replaced with
# cygrunsrv of newly installed cygwin
#
# supported cygwin versions:
#   x86_64
#   x86
def packages_cygwin(version)
  setup_exe = "setup-#{version}.exe"

  return <<-EOF
    mkdir -p /cygdrive/c/cygwin
    # Download setup.exe
    powershell -Command '(New-Object System.Net.WebClient).DownloadFile("https://www.cygwin.com/#{setup_exe}","C:\\cygwin\\#{setup_exe}")'

    # Create a .bat file performing the installation
    echo '
    REM --- Change to use different CygWin platform and final install path
    set CYGSETUP=#{setup_exe}
    REM --- Install build version of CygWin in a subfolder
    set OURPATH=%cd%
    set CYGBUILD="C:\\cygwin\\CygWin"
    set CYGMIRROR=http://mirrors.kernel.org/sourceware/cygwin/
    set BASEPKGS=openssh,rsync
    set BUILDPKGS=p7zip
    REM set BUILDPKGS=python3,python3-setuptools,python3-devel,binutils,gcc-core
    %CYGSETUP% -q -B -o -n -R %CYGBUILD% -L -D -s %CYGMIRROR% -P %BASEPKGS%,%BUILDPKGS%
    cd /d C:\\cygwin\\CygWin\\bin
    regtool set /HKLM/SYSTEM/CurrentControlSet/Services/OpenSSHd/ImagePath "C:\\cygwin\\CygWin\\bin\\cygrunsrv.exe"
    bash -c "ssh-host-config --no"
    bash -c "chown sshd_server /cygdrive/c/cygwin/CygWin/var/empty"
    ' > /cygdrive/c/cygwin/install.bat

    # Prepare and source our profile
    echo "alias mkdir='mkdir -p'" > ~/.profile
    echo "export CYGWIN_ROOT=/cygdrive/c/cygwin/CygWin" >> ~/.profile
    echo 'export PATH=$CYGWIN_ROOT/bin:$PATH' >> ~/.profile

    echo '' > ~/.bash_profile

    cmd.exe /c 'setx /m PATH "C:\\cygwin\\CygWin\\bin;%PATH%"'
    source ~/.profile

    cd /cygdrive/c/cygwin && cmd.exe /c install.bat

    echo 'db_home: windows' > $CYGWIN_ROOT/etc/nsswitch.conf
  EOF
end

def packages_chocolatey()
  return <<-EOF
    powershell " \
       Set-ExecutionPolicy Bypass -Scope Process -Force; \
       [System.Net.ServicePointManager]::SecurityProtocol = \
           [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; \
       iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
  EOF
end

def choco_install(packages)
  # Using the full path for `choco` here saves the need to reload the machine
  # to get $PATH set.
   return <<-EOF
    echo > install-choco.ps1 '
    $env:Path += ";$($env:ProgramData)/Chocolatey/bin"
    choco install -y #{packages}
    ' ; powershell -NoProfile -ExecutionPolicy Bypass -File ./install-choco.ps1
  EOF
end

def windows_disable_updates()
  # TODO: Add more entries to really disable all auto-updates and reboots.
  # Also disable Cortana
  return <<-EOF
    echo > no-updates.ps1 '
    $base = "HKLM:/Software/Policies/Microsoft/Windows/"
    New-Item -Force -Path  "$base/WindowsUpdate" | Out-Null
    New-Item -Force -Path  "$base/WindowsUpdate/AU" | Out-Null
    New-ItemProperty -Path "$base/WindowsUpdate/AU" -Name "NoAutoUpdate" -Value 1 -PropertyType DWORD -Force | Out-Null
    Get-ItemProperty       "$base/WindowsUpdate/AU"
    New-Item -Force  -Path "$base/Windows Search" | Out-Null
    New-ItemProperty -Path "$base/Windows Search" -Name "AllowCortana" -Value 0 -PropertyType DWORD -Force | Out-Null
    Get-ItemProperty       "$base/Windows Search"
    ' ; powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./no-updates.ps1
  EOF
end

def build_bootloader(boxname)
  return <<-EOF
    set -x
    for d in /vagrant /cygdrive/c/vagrant ; do
       if [ -d "$d/bootloader" ] ; then
          cd "$d"/bootloader
          break
       fi
    done
    for d in /cygdrive/c $HOME ; do
       if [ -d "$d/mingw64" ] ; then
          export PATH="$d/mingw64/bin:$d/mingw64/opt/bin:$PATH"
          break
       fi
    done
    python3 ./waf all
    python3 ./waf all CC=i686-linux-gnu-gcc
  EOF
end

def build_bootloader_on_windows(boxname)
  return <<-EOF
    cd c:/vagrant/bootloader
    py -3 ./waf all --target-arch=64bit
    py -3 ./waf all --target-arch=32bit
  EOF
end

def build_bootloader_target_win32(boxname)
  return <<-EOF
    cd /vagrant/bootloader
    py -3 ./waf all CC=i686-w64-mingw32-gcc
    py -3 ./waf all CC=x86_64-w64-mingw32-gcc
  EOF
end

def build_bootloader_target_osx(boxname)
  return <<-EOF
    cd /vagrant/bootloader
    # Unpack the SDK and the build-tools (cctools)
    if [ ! -d ~/osxcross ] ; then
        mkdir -p ~/osxcross
        tar -C ~/osxcross --xz -xf /vagrant/bootloader/_sdks/osx/osxcross.tar.xz
    fi
    PATH=~/osxcross/bin:$PATH
    echo $PATH
    export LD_LIBRARY_PATH=~/osxcross/lib:$LD_LIBRARY_PATH
    export MACOSX_DEPLOYMENT_TARGET=10.13
    export CC=$(basename -a ~/osxcross/bin/x86_64-*-clang)
    python3 ./waf all --clang
  EOF
end

Vagrant.configure(2) do |config|
  # Do not leak information about usage by by update-checks.
  config.vm.box_check_update = false

  # Let the VM access the dirstribution folder (the parent directory) on the
  # host machine via the default shared folder. We need to use the parent
  # directory since for building the windows bootloader we need to access some
  # image files in ../PyInstaller/bootloader/images/.
  config.vm.synced_folder "..", "/vagrant"

  config.vm.provider :virtualbox do |v|
    v.gui = is_set?(ENV['GUI'])
    v.cpus = 1
  end

  #--- Linux 64 bit, using Debian 8 from boxcutter ---
  config.vm.define "linux64" do |b|
    b.vm.box = DEBIAN
    b.vm.provider :virtualbox do |v|
      v.memory = 768
    end
    b.vm.provision "packages debianoid", :type => :shell, :inline => packages_debianoid

    if is_env?("TARGET", "WINDOWS")
      b.vm.provision "build bootloader for windows",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => build_bootloader_target_win32("linux64")
    elsif is_env?("TARGET", "OSX")
      # We need clang-11 to build universal2 bootloader.
      b.vm.provision "packages clang11 debianoid", :type => :shell, :inline => packages_clang11_debianoid, :keep_color => true
      if (not File.exist?("_sdks/osx/osxcross.tar.xz"))
        abort("\nERROR: OS X cross SDK is missing. Please run once the "\
              "`build-osxcross` machine.\n\n")
      end
      b.vm.provision "build bootloader for osx",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => build_bootloader_target_osx("linux64")
    else
      b.vm.provision "build bootloader",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => build_bootloader("linux64")
    end
  end

  #--- Build the Darwin SDK and tools ---
  config.vm.define "build-osxcross" do |b|
    b.vm.box = DEBIAN
    b.vm.provider :virtualbox do |v|
      v.memory = 768
    end
    # NOTE: the osxcross scripts install clang-7, so there's no point in
    # installing clang-11 here... (and the SDK builds just fine with
    # clang-7).
    b.vm.provision "packages osxcross debianoid", :type => :shell,
        :inline => packages_osxcross_debianoid,
        :keep_color => true
    b.vm.provision "prepare osxcross debianoid", :type => :shell,
        :inline => prepare_osxcross_debianiod,
        :keep_color => true
    # Reload in case the kernel changed
    b.vm.provision :reload
    b.vm.provision "build osxcross", :type => :shell,
        :inline => build_osxcross,
        :keep_color => true
  end

  #--- Windows 64 bit
  #- This box requires interaction, automated build is not possible at
  #- the moment. Please see the README for more information.
  config.vm.define "windows10" do |b|
    b.vm.box = "Microsoft/EdgeOnWindows10"
    b.vm.guest = :windows
    b.vm.boot_timeout = 180
    b.vm.graceful_halt_timeout = 120

    b.ssh.shell = "sh -l"
    b.ssh.username = "IEUser"
    b.ssh.password = "Passw0rd!"
    b.ssh.insert_key = false

    # Disable auto-update - only partially working
    b.vm.provision "disable auto-updates", :type => :shell,
        :privileged => false, :inline => windows_disable_updates(),
        :keep_color => true

    b.vm.provider :virtualbox do |v|
      v.memory = 2048
    end

    if is_set?(ENV['MINGW'])
      #-- Build using MinGW-64 in cygwin
      # Install cygwin to get rsync and 7zip
      b.vm.provision "packages cygwin", :type => :shell, :privileged => false,
        :inline => packages_cygwin("x86_64")

      # Reload to get into the new cygwin environment
      b.vm.provision :reload

      # Install mingw-w64 into $HOME
      # Note: Our wscript file currently doesn't support cross building (here:
      # from cygwin to win32), so be can't use mingw coming with cygwin.
      b.vm.provision "download mingw-w64 archive", :type => :file,
        source: "~/Downloads/x86_64-6.2.0-release-posix-sjlj-rt_v5-rev1.7z",
        destination: "Downloads/mingw-w64.7z"  # will go into $HOME
      b.vm.provision "install mingw-w64", :type => :shell, :privileged => false,
        :inline => "7z x -o$HOME $HOME/Downloads/mingw-w64.7z"

      # Build the bootloader
      b.vm.provision "build bootloader", :type => :shell, :privileged => false,
        :inline => build_bootloader("windows10-64"),
        :keep_color => true

    else
      #-- Build using Visual C++
      b.vm.provision "install chocolatey",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => packages_chocolatey()
      b.vm.provision "install packages",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => choco_install("python3 visualstudio2019-workload-vctools")
      b.vm.provision "build bootloader",
        :type => :shell, :privileged => false, :keep_color => true,
        :inline => build_bootloader_on_windows("windows10")
    end

  end

end
