###############################################################################
#  INTEL CONFIDENTIAL
#
#  Copyright (c) 2021 Intel Corporation
#  All Rights Reserved.
#
#  This software and the related documents are Intel copyrighted materials,
#  and your use of them is governed by the express license under which they
#  were provided to you ("License"). Unless the License provides otherwise,
#  you may not use, modify, copy, publish, distribute, disclose or transmit this
#  software or the related documents without Intel's prior written permission.
#
#  This software and the related documents are provided as is, with no express or
#  implied warranties, other than those that are expressly stated in the License.
#################################################################################

###############################################################################
#
# This is a utility to ease building P4 programs with P4Studio
#
# NOTE: Before using this tool, please ensure SDE is installed at $SDE_INSTALL
#
# This file can be used as is from the current location or copied to an 
# external location. The standard CMake build semantics apply here.
# cmake <location-of-this-file> <option1> <option2>
#
# This utility uses the P4Build.cmake module and other modules supplied with SDE.
# Additionally, the location of SDE artifacts like P4 compiler and other tools
# are also resolved automatically based on $SDE_INSTALL.
#
# CMAKE_MODULE_PATH is a required option when invoking this file
#   - This option allows cmake to find and use the functions defined in 
#     P4Build.cmake and find other modules like FindThrift.cmake.
# CMAKE_INSTALL_PREFIX is the location of bf-p4c and other artifacts
#   - This is typically the location where the SDE build artifacts are installed
#     Tools like bf-p4c, p4c-gen-bfrt-conf, etc are present in this location in
#     addition to built libraries like libdriver.so.
#
# P4-14 PTF python test scripts need Python-Thrift bindings to invoke PD API
# calls over Thrift-RPC. Similarly the process that hosts the PD API library will
# need C-Thrift bindings to process Thrift-RPC messages and make 'C' PD API calls.
#
# Using option WITHPD=on, will disable generating BF-RT schema and instead
# generate PD artifacts like libpd.so, libpdcli.so, etc.
#
# Using THRIFT-DRIVER=on, will enable building libpdthrift.so.
# Please ensure the third-party Thrift library is installed if this option is used
#
# Example
# =======
# User program location: /myprogram/test.p4
# SDE location: /sde
# SDE_INSTALL location: /sde/install
# This file location: /sde/p4studio/
#
# mkdir /build && cd /build
# cmake /sde/p4studio/ \
#       -DCMAKE_INSTALL_PREFIX=$SDE_INSTALL \
#       -DCMAKE_MODULE_PATH=$SDE/cmake      \
#       -DP4_NAME="myprogram"               \
#       -DP4_PATH=/myprogram/test.p4
# make myprogram && make install
#
# The built artifacts are installed to $SDE_INSTALL
#
# The majority of options for this tool are available as CMake options. The
# rest are to be supplied as -D option strings. Please refer to the list of
# available options below
#
# List of string options
# ======================
# Option        Description             Default     Comment
# ------------------------------------------------------------
# P4_NAME       P4 program name         None        Mandatory
# P4_PATH       P4 program path         None        Mandatory
# P4_LANG       p4-14/p4-16             p4-16       Optional
# P4FLAGS       P4 compiler flags       ""          Optional
# P4PPFLAGS     P4 preprocessor flags   ""          Optional
# PDFLAGS       Program dependent flags ""          Optional
#
# Artifacts installed
# ===================
#  - P4 program artifacts
#      * data files generated for bf-drivers
#        to $SDE_INSTALL/share/p4/<$P4_NAME>/
#          - context.json: pipeline resource context for the driver
#          - tofino.bin/tofnio2.bin: configuration based on Tofino device
#
#      * conf files needed by switchd application for PTF tests
#        to $SDE_INSTALL/share/p4/targets/<$P4_NAME>*.conf
#
#  - BF-Runtime generated artifacts (when compiled with P4_LANG=p4-16)
#      * $SDE_INSTALL/share/p4/<$P4_NAME>/bf-rt.json
#
#
# The following shared libraries are installed to $SDE_INSTALL/lib/<$P4_NAME>/
#
# Additional build artifacts installed when built with WITHPD=on
#  - P4 program dependent auto-generated artifacts
#      * header files for auto-generated P4 dependent (PD) library
#        to $SDE_INSTALL/include/<$P4_NAME>/pd
#      * libraries for auto-generated P4 dependent sources
#          - libpd.so
#      * libraries for auto-generated CLI
#          - libpdcli.so
#
# Additional build artifacts installed when built with THRIFT-DRIVER=on
#  - P4 program dependent auto-generated artifacts
#      * libraries for auto-generated Thrift-RPC bindings
#          - libpdthrift.so
#      * Python modules for calling from PTF
#
# Artifact install tree
# =====================
#  ├── bin
#  ├── include
#  │   └── tofinopd
#  │       └── <$P4_NAME>
#  │           └── pd
#  │               └── pd.h
#  ├── lib
#  │   ├── tofinopd
#  │   │   └── <$P4_NAME>
#  │   │       ├── libpd.so
#  │   │       ├── libpdcli.so
#  │   │       └── libpdthrift.so
#  │   └── python2.7
#  │       └── site-packages
#  │           └── tofinopd
#  │               └── <$P4_NAME>
#  │                   └── p4_pd_rpc
#  │                       ├── constants.py
#  │                       ├── __init__.py
#  │                       ├── <$P4_NAME>.py
#  │                       └── ttypes.py
#  └── share
#      ├── p4
#      │   └── targets
#      │       ├── tofino
#      │       │    └── <$P4_NAME>.conf
#      │       └── tofino2
#      │            └── <$P4_NAME>.conf
#      ├── tofinopd
#      │   └── <$P4_NAME>
#      │       ├── bf-rt.json
#      │       └── <pipe name>
#      │           ├── context.json
#      │           └── tofino.bin
#      └── tofino2pd
#          └── <$P4_NAME>
#              ├── bf-rt.json
#              └── <pipe name>
#                  ├── context.json
#                  └── tofino2.bin
#
###############################################################################

cmake_minimum_required(VERSION 3.1)
project(p4builder VERSION 0.1 LANGUAGES C CXX)

if((NOT DEFINED ENV{SDE}) AND (NOT DEFINED SDE))
  message(FATAL_ERROR "SDE environment variable is not set. Please set it and try again")
else()
  set(SDE $ENV{SDE})
endif()
message(STATUS "\nUsing SDE: ${SDE}")

set(SDE_CMAKE_MODULE_PATH ${SDE}/cmake)
list(APPEND CMAKE_MODULE_PATH ${SDE_CMAKE_MODULE_PATH})

if(NOT DEFINED ENV{SDE_INSTALL})
  set(SDE_INSTALL "${SDE}/install/")
  message(WARNING "\nSDE_INSTALL environment variable is not set. Automatically setting SDE_INSTALL to ${SDE_INSTALL}")
else()
  set(SDE_INSTALL $ENV{SDE_INSTALL})
  message(STATUS "\nUsing SDE_INSTALL: ${SDE_INSTALL}")
endif()

if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set(CMAKE_INSTALL_PREFIX ${SDE_INSTALL} CACHE PATH "..." FORCE)
endif()
set(CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}")

# options
option(TOFINO        "Build P4 for tofino target" OFF)
option(TOFINO2       "Build P4 for tofino2 target" OFF)
option(TOFINO2M      "Build P4 for tofino2m target" OFF)
option(PSA           "Build P4 for PSA architecture" OFF)
option(V1MODEL       "Build P4 for v1model architecture" OFF)
option(THRIFT-DRIVER "Build with thrift support" OFF)
option(WITHPD        "Build with PD artifacts" OFF)

set(CMAKE_BUILD_TYPE "RelWithDebInfo")
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_INSTALL_PREFIX}/lib")
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)


include(PythonDependencies)

if (NOT TOFINO AND NOT TOFINO2 AND NOT TOFINO2M AND NOT TOFINO3)
  set(TOFINO ON CACHE BOOL "Enable Tofino as default" FORCE)
  message(STATUS "\nTarget option missing. Using TOFINO\n")
endif()

# dummy targets to satisfy dependencies
add_custom_target(bf-p4c)
add_custom_target(driver)

# p4_lang
if(NOT P4_LANG)
  set(P4_LANG "p4-16" CACHE STRING "" FORCE)
  message(STATUS "\nP4_LANG missing. Using ${P4_LANG}")
else()
  message(STATUS "\nP4_LANG: ${P4_LANG}")
endif()

if (${P4_LANG} STREQUAL "p4-14")
  set(THRIFT-DRIVER ON CACHE BOOL "Build with Thrift support as default for p4-14" FORCE)
  set(WITHPD ON CACHE BOOL "Build with PD artifacts as default for p4-14" FORCE)
  set(V1MODEL ON CACHE BOOL "V1MODEL only supported arch for p4-14" FORCE)
endif()

if(THRIFT-DRIVER)
  find_package(Thrift REQUIRED)
endif()

find_program(P4C bf-p4c REQUIRED)
message("P4C: " ${P4C})
find_program(P4C-GEN-BFRT-CONF p4c-gen-bfrt-conf REQUIRED)
message("P4C-GEN_BRFT-CONF: " ${P4C-GEN-BFRT-CONF})
find_program(P4C-MANIFEST-CONFIG p4c-manifest-config REQUIRED)
message("P4C-MANIFEST-CONFIG: " ${P4C-MANIFEST-CONFIG})
if (WITHPD)
  find_program(PDGEN generate_tofino_pd REQUIRED)
  set(PDGEN_COMMAND ${PYTHON_COMMAND} ${PDGEN})
  message("PDGEN: " ${PDGEN})
  find_program(PDGENCLI gencli REQUIRED)
  set(PDGENCLI_COMMAND ${PYTHON_COMMAND} ${PDGENCLI})
  message("PDGENCLI: " ${PDGENCLI})
  find_program(PDSPLIT split_pd_thrift.py REQUIRED)
  set(PDSPLIT_COMMAND ${PYTHON_COMMAND} ${PDSPLIT})
  message("PDSPLIT: " ${PDSPLIT})

endif()

include(P4Build)

if (TOFINO)
  set(P4_tofino_ARCHITECTURE "tna")
endif()
if (TOFINO2 OR TOFINO2M)
  set(P4_tofino2_ARCHITECTURE "t2na")
endif()

if (PSA)
  set(P4_tofino_ARCHITECTURE "psa")
  set(P4_tofino2_ARCHITECTURE "psa")
endif()

if (V1MODEL)
  set(P4_tofino_ARCHITECTURE "v1model")
  set(P4_tofino2_ARCHITECTURE "v1model")
endif()

# p4_path is mandatory
if(NOT P4_PATH)
  message(FATAL_ERROR "P4 program path (P4_PATH) missing")
else()
  message(STATUS "\nP4_PATH: ${P4_PATH}")
endif()
if(NOT EXISTS ${P4_PATH})
  message(FATAL_ERROR "Invalid P4_PATH: ${P4_PATH}")
endif()

if(NOT P4_NAME)
  # NAME_WE is a mode used to get file name without directory/longest extension
  get_filename_component(P4_NAME "${P4_PATH}" NAME_WE)
endif()
message("P4_NAME: ${P4_NAME}")

if(NOT P4FLAGS)
  set(P4FLAGS "" CACHE STRING "" FORCE)
else()
  message(STATUS "\nP4FLAGS: ${P4FLAGS}")
endif()

if(NOT P4PPFLAGS)
  set(P4PPFLAGS "" CACHE STRING "" FORCE)
else()
  message(STATUS "\nP4PPFLAGS: ${P4PPFLAGS}")
endif()

if(NOT PDFLAGS)
  set(PDFLAGS "" CACHE STRING "" FORCE)
else()
  message(STATUS "\nPDFLAGS: ${PDFLAGS}")
endif()

add_custom_target(${P4_NAME} ALL)
if (TOFINO)
  if (WITHPD)
    p4_build_pd_target(${P4_NAME} ${P4_tofino_ARCHITECTURE} "tofino" ${P4_PATH})
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${P4_NAME}/tofino/${P4_NAME}.conf DESTINATION share/p4/targets/tofino)
  else()
    p4_build_target(${P4_NAME} ${P4_tofino_ARCHITECTURE} "tofino" ${P4_PATH})
  endif()
  add_dependencies(${P4_NAME} ${P4_NAME}-tofino)
endif()

if (TOFINO2)
  if (WITHPD)
    p4_build_pd_target(${P4_NAME} ${P4_tofino2_ARCHITECTURE} "tofino2" ${P4_PATH})
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${P4_NAME}/tofino2/${P4_NAME}.conf DESTINATION share/p4/targets/tofino2)
  else()
    p4_build_target(${P4_NAME} ${P4_tofino2_ARCHITECTURE} "tofino2" ${P4_PATH})
  endif()
  add_dependencies(${P4_NAME} ${P4_NAME}-tofino2)
endif()

if (TOFINO2M)
  if (WITHPD)
    p4_build_pd_target(${P4_NAME} ${P4_tofino2_ARCHITECTURE} "tofino2m" ${P4_PATH})
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${P4_NAME}/tofino2m/${P4_NAME}.conf DESTINATION share/p4/targets/tofino2m)
  else()
    p4_build_target(${P4_NAME} ${P4_tofino2_ARCHITECTURE} "tofino2m" ${P4_PATH})
  endif()
  add_dependencies(${P4_NAME} ${P4_NAME}-tofino2m)
endif()



add_custom_target(uninstall
  COMMAND ${CMAKE_COMMAND} -DBINARY_DIR="${CMAKE_BINARY_DIR}" -DCMAKE_COMMAND="${CMAKE_COMMAND}" -P ${SDE_CMAKE_MODULE_PATH}/CmakeUninstall.cmake)
