#!/usr/bin/env python3
# encoding: utf-8
#
#   python/wscript - part of TRE
#   TRE python extension waf build script.
#
#   This software is released under a BSD-style license.
#   See the file LICENSE for details and copyright.
#

import sys,os,venv,shutil
from waflib import Logs,Options
from waflib.Task import Task

import python_unit_tests

debug_python_venv = False

class no_lib64_symlink_EnvBuilder(venv.EnvBuilder):
   """
   Override the EnvBuilder code that does a symlink to avoid problems with remote filesystems (see the use in configure() below).
   """

   def __init__(self,*args,**kwargs):
      super().__init__(*args,**kwargs)

   def ensure_directories(self,env_dir):
      # 1. Create the lib64 directory first, which stops the super().ensure_directories() from creating it as a symlink
      lib64_path = os.path.join(env_dir,"lib64")
      if not os.path.exists(lib64_path):
         os.makedirs(lib64_path)
      # 2. Use super().ensure_directories(env_dir) to do the rest of the work.
      context = super().ensure_directories(env_dir)
      # 3. Copy everything below lib to lib64.
      shutil.copytree(os.path.join(env_dir,"lib"),lib64_path,dirs_exist_ok=True)
      # return the super's return value
      return context

def options(opt):
   opt.load("python")

def configure(cfg):
   if Options.options.log_wscripts:
      Logs.pprint('CYAN','Configuring in {0:s}, VARIANT{{{1:s}}}'.format(cfg.path.abspath(),cfg.env['VARIANT']))
   # Force loading python3 instead of python
   if "windows" != cfg.env.PLATSYS:
      cfg.find_program("python3",var="PYTHON")
   cfg.load("python")
   cfg.check_python_headers()
   # cfg.test_pyext("c")
   # The python extension needs to include tre.h, but we may not
   # have it in the system or the build envrionment like setup.py does,
   # so get it from the TRE lib directory by:
   # 1) setting a command line define to indicate building with waf PYEXT,
   cfg.env.append_unique("DEFINES_PYEXT","HAVE_PYEXT")
   # 2) adding a command line include to PYEXT,
   cfg.env.append_unique("INCLUDES_PYEXT","../lib")
   # 3) checking for HAVE_PYEXT in the source file and including "tre.h" instead of <tre/tre.h>.
   #========== testing --> make sure we have a virtual env set up in the build directory.
   # PYTHON_VERSION' '3.9' lib/python3.9/site-packages
   for cfg_key in cfg.all_envs:
      if "" == cfg_key or cfg.all_envs[""].PLATSYS == cfg_key:
         # Skip the original env, and the platform specific one.  All we want here is the variants.
         continue
      var_env = cfg.all_envs[cfg_key]
      cfg.setenv(cfg_key)
      if not cfg.env.viable:
         # We aren't building libtre here, so there is no point in looking at the python extension.
         continue
      if not cfg.env.variant_ap:
         # The python extension needs approximate matching.
         # We can still build libtre but the python extension won't work.
         cfg.env.viable_python_ext = False
         continue
      cfg.env.viable_python_ext = True
      venv_path = cfg.bldnode.abspath() + '/'
      venv_path +=  ((cfg.env.VARIANT + '/') if len(cfg.env.VARIANT) > 0 else '')
      venv_path +=  cfg.path.get_bld().path_from(cfg.bldnode)
      var_env["VENV_PATH"] = venv_path
      # Logs.pprint("YELLOW","venv path {{{!s}}}".format(venv_path))
      # Test for 64-bit lib needed (but only on some platforms)
      symlinks_work = True
      use_lib64 = False
      if sys.maxsize > 2**32 and "windows" != var_env.PLATSYS:
         # The built in venv.EnvBuilder code will put in a symlink lib64 -> lib on posix-like
         # systems because many distros do it and it's too complicated for python to try and
         # untangle it.  Unfortunately, if we want to run this build over any kind of remote
         # filesyste (e.g. NFS, SMB, VM shared folder, etc.) symlinks are not going to work.
         # Try making a symlink to a directory to see if we need to fiddle things.
         lib64_path = os.path.join(venv_path,"lib64")
         if os.path.exists(lib64_path):
            use_lib64 = True
         else:
            dummy_target_path = os.path.join(venv_path,"dummy_target")
            dummy_symlink_path = os.path.join(venv_path,"dummy_symlink")
            try:
               os.makedirs(dummy_target_path)
               os.symlink(dummy_target_path,dummy_symlink_path)
            except:
               symlinks_work = False
               use_lib64 = True
            if os.path.exists(dummy_symlink_path):
               os.remove(dummy_symlink_path)
            if os.path.exists(dummy_target_path):
               os.rmdir(dummy_target_path)
      var_env["VENV_USE_LIB64"] = use_lib64
      if not symlinks_work:
         # We only need one binary to go under lib<whatever> (and nothing from the base python),
         # so just override the EnvBuilder class with code that builds both lib and lib64 as
         # ordinary directories, and put our binary in when it gets built.
         if debug_python_venv:
            Logs.pprint('RED','Creating symlink-free python venv in {0:s}'.format(venv_path))
         venv_builder = no_lib64_symlink_EnvBuilder()
      else:
         if debug_python_venv:
            Logs.pprint('GREEN','Creating ordinary python venv in {0:s}'.format(venv_path))
         venv_builder = venv.EnvBuilder()
      venv_builder.create(venv_path)
   cfg.setenv("")
   # Turn off install of .pyc and .pyo files (and hence building them) since we're not
   # building anything with python source, only testing with it.
   cfg.env.PYC = 0
   cfg.env.PYO = 0

def build(bld):
   if not bld.env.viable_python_ext:
      if Options.options.log_wscripts:
         msg = "Skipping build in {:s}, VARIANT{{{:s}}} (python extension not viable in variant)"
         Logs.pprint("NORMAL",msg.format(bld.path.abspath(),bld.env['VARIANT']))
      return
   if Options.options.log_wscripts:
      Logs.pprint('NORMAL','Building in {:s}, VARIANT{{{:s}}}'.format(bld.path.abspath(),bld.env['VARIANT']))
   tre_py_nodes = bld.path.ant_glob('*.c')
   # Logs.pprint("CYAN","env for python extent:\n{!s}".format(bld.env))
   # Logs.pprint("YELLOW","VENV_PATH = {:s}".format(bld.env["VENV_PATH"]))
   # We want to build the extension module right in the virtual environment's site-packages directory
   # so we can test it without having to copy it and play with indirect dependencies.
   # The "pyext" feature will modify the final component of the path as needed (e.g. adding the PYTAG).
   using_venv = ("VENV_PATH" in bld.env)
   py_ext_name = "tre"
   if using_venv:
      py_ext_path = os.path.join(bld.env.VENV_PATH,"lib","python"+bld.env.PYTHON_VERSION,"site-packages",py_ext_name)
   else:
      py_ext_path = py_ext_name
   # Use the static libtre for the python extension, so we only have to install one library for testing.
   tre_python = bld(features=["c","cshlib","pyext"],source=tre_py_nodes,target=py_ext_path,name="tre_python",use=["LIBTRE","static_libtre"])
   if using_venv and bld.env.VENV_USE_LIB64:
      # Just build it again in lib64, because it is too complicated to figure out where python is going to look for it.
      py_ext64_path = os.path.join(bld.env.VENV_PATH,"lib64","python"+bld.env.PYTHON_VERSION,"site-packages",py_ext_name)
      tre_python64 = bld(features=["c","cshlib","pyext"],source=tre_py_nodes,target=py_ext64_path,
                         name="tre_python64",use=["LIBTRE","static_libtre"])
   if bld.changeable_task_group and bld.env.BUILD_TESTS and using_venv:
      # Build and run the tests.
      tre_python.post() # So we can get the link_task.outputs[0]
      # Logs.pprint("YELLOW","tre_python.tasks = {!s}".format(tre_python.tasks))
      # Logs.pprint("YELLOW","type(tre_python.link_task) = {!s}".format(type(tre_python.link_task)))
      # Logs.pprint("YELLOW","tre_python.link_task = {!s}".format(tre_python.link_task))
      # Logs.pprint("YELLOW","type(tre_python.link_task.outputs[0]) = {!s}".format(type(tre_python.link_task.outputs[0])))
      # Logs.pprint("YELLOW","tre_python.link_task.outputs[0] = {!s}".format(tre_python.link_task.outputs[0]))
      py_ext_node = tre_python.link_task.outputs[0] # the extension node
      test_node = bld.path.find_node("example.py")
      # make sure that a change of the extension from tre_python results in another test run
      # by adding it as a manual dependency of the "example.py" program.
      # Logs.pprint("YELLOW","test_node = {!s}".format(test_node.abspath()))
      # Logs.pprint("YELLOW","py_ext_node = {!s}".format(py_ext_node.abspath()))
      bld.add_manual_dependency(test_node,py_ext_node)
      # Put the tests in a different group from the build targets to avoid having to figure out indirect dependencies.
      bld.set_group('test_tasks')
      ext_test = bld(features="python_unit_test",source=[test_node],DEBUG_ME=False,install_path=None)
      # Go back to the regular task group
      bld.set_group('build_tasks')
