#!/usr/bin/env python3
#
# Copyright 2025 Google LLC
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
"""Builds Dawn using CMake.

This script configures and builds Dawn using CMake and Ninja. It then
discovers all object files for the specified libraries and combines them
into static archives (.a files) using `ar`. It also copies Dawn's generated
headers to the location GN expects.
"""

import argparse
import os
import shutil
import subprocess
import sys

from cmake_utils import (add_common_cmake_args, combine_into_library,
                         discover_dependencies, get_cmake_os_cpu,
                         get_windows_settings, quote_if_needed, write_depfile,
                         get_third_party_locations)


def gn_bool_to_cmake(s):
  if s.lower() == "true":
    return "ON"
  return "OFF"


def main():
  parser = argparse.ArgumentParser(description="Build Dawn using CMake.")
  add_common_cmake_args(parser)
  parser.add_argument(
      "--gen_dir",
      required=True,
      help="Directory for generated files (e.g., depfile and headers).")
  parser.add_argument(
      "--android_ndk_path", default="", help="Path to the Android NDK.")
  parser.add_argument(
      "--android_platform",
      default="",
      help="Android platform (e.g., android-29).")
  parser.add_argument(
      "--dawn_enable_d3d11", default="false", help="Enable D3D11 backend.")
  parser.add_argument(
      "--dawn_enable_d3d12", default="false", help="Enable D3D12 backend.")
  parser.add_argument(
      "--dawn_enable_opengles", default="false", help="Enable GLES backend.")
  parser.add_argument(
      "--dawn_enable_metal", default="false", help="Enable Metal backend.")
  parser.add_argument(
      "--dawn_enable_vulkan", default="false", help="Enable Vulkan backend.")
  args = parser.parse_args()

  cmake_exe = shutil.which("cmake")
  if not cmake_exe:
    print("Error: cmake not found in PATH.")
    sys.exit(1)

  ninja_exe = shutil.which("ninja")
  if not ninja_exe:
    print("Error: ninja not found in PATH.")
    sys.exit(1)

  target_os, target_cpu = get_cmake_os_cpu(args.target_os, args.target_cpu)

  output_path = args.output_path
  gen_dir = args.gen_dir
  # The headers are a dependency for all libraries.
  # We want to build the other listed dawn components into one big library.
  build_targets = ["webgpu_headers_gen", "dawn_proc", "dawn_native"]
  depfile_path = args.depfile_path

  script_dir = os.path.dirname(os.path.realpath(__file__))

  dawn_dir = os.path.join(script_dir, "..", "externals", "dawn")
  build_dir = args.build_dir

  configure_cmd = [
      cmake_exe,
      "-S",
      dawn_dir,
      "-B",
      build_dir,
      "-G",
      "Ninja",
      f"-DCMAKE_MAKE_PROGRAM={ninja_exe}",
      f"--install-prefix={os.path.abspath(gen_dir)}",
      f"-DCMAKE_SYSTEM_NAME={target_os}",
      f"-DCMAKE_SYSTEM_PROCESSOR={target_cpu}",
      "-DDAWN_BUILD_MONOLITHIC_LIBRARY=OFF",
      f"-DCMAKE_BUILD_TYPE={args.build_type}",
      # Explicitly set the C++ standard to avoid issues with CMake's feature
      # detection for Clang on Windows.
      "-DCMAKE_CXX_STANDARD=20",
      "-DCMAKE_CXX_STANDARD_REQUIRED=ON",
      "-DCMAKE_CXX_EXTENSIONS=OFF",
      "-DDAWN_FORCE_SYSTEM_COMPONENT_LOAD=ON", # https://g-issues.chromium.org/issues/399358291
      "-DDAWN_ENABLE_INSTALL=OFF",
      "-DTINT_ENABLE_INSTALL=OFF",
      f"-DDAWN_ENABLE_D3D11={gn_bool_to_cmake(args.dawn_enable_d3d11)}",
      f"-DDAWN_ENABLE_D3D12={gn_bool_to_cmake(args.dawn_enable_d3d12)}",
      f"-DDAWN_ENABLE_OPENGLES={gn_bool_to_cmake(args.dawn_enable_opengles)}",
      f"-DDAWN_ENABLE_METAL={gn_bool_to_cmake(args.dawn_enable_metal)}",
      f"-DDAWN_ENABLE_VULKAN={gn_bool_to_cmake(args.dawn_enable_vulkan)}",
  ]
  configure_cmd += get_third_party_locations()

  if args.enable_rtti:
    configure_cmd.append("-DDAWN_ENABLE_RTTI=ON")

  cxx_flags = args.cxx_flags or []
  ld_flags = args.ld_flags or []

  if target_os == "Windows":
    win_cfgs, win_cxx, win_ld = get_windows_settings(args)
    configure_cmd += win_cfgs
    cxx_flags += win_cxx
    ld_flags += win_ld

    # The D3D backend requires the HLSL writer.
    configure_cmd.append("-DTINT_BUILD_HLSL_WRITER=ON")
  else:
    configure_cmd.append("-DTINT_BUILD_HLSL_WRITER=OFF")
    cxx_flags.append("-w") # Silence warnings

  if cxx_flags:
    c_cxx_flags_str = " ".join(cxx_flags)
    # -fno-rtti is not a valid C flag.
    c_flags = [f for f in cxx_flags if f != "-fno-rtti"]
    c_flags_str = " ".join(c_flags)
    configure_cmd.append(f"-DCMAKE_C_FLAGS={c_flags_str}")
    configure_cmd.append(f"-DCMAKE_CXX_FLAGS={c_cxx_flags_str}")

  if ld_flags:
    ld_flags_str = " ".join(ld_flags)
    configure_cmd.append(f"-DCMAKE_EXE_LINKER_FLAGS={ld_flags_str}")
    configure_cmd.append(f"-DCMAKE_SHARED_LINKER_FLAGS={ld_flags_str}")
    configure_cmd.append(f"-DCMAKE_MODULE_LINKER_FLAGS={ld_flags_str}")

  if target_os == "Android":
    configure_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={args.android_ndk_path}/build/cmake/android.toolchain.cmake")
    configure_cmd.append(f"-DANDROID_ABI={target_cpu}")
    configure_cmd.append(f"-DANDROID_PLATFORM={args.android_platform}")
  else:
    configure_cmd.append(f"-DCMAKE_C_COMPILER={args.cc.replace(os.sep, '/')}")
    configure_cmd.append(f"-DCMAKE_CXX_COMPILER={args.cxx.replace(os.sep, '/')}")

  if target_os == "Darwin" or target_os == "iOS":
    configure_cmd.append(f"-DCMAKE_OSX_ARCHITECTURES={target_cpu}")

  env = os.environ.copy()
  # Don't write .pyc files, which can cause race conditions when building
  # tint and dawn in parallel.
  env["PYTHONDONTWRITEBYTECODE"] = "1"

  if target_os == "Windows":
    # Set the WINDOWSSDKDIR environment variable to ensure that Dawn's
    # DetectWindowsSDK function uses the correct, hermetic SDK path.
    env["WINDOWSSDKDIR"] = args.win_sdk

  print(" ".join(configure_cmd))
  print("with environment", env)
  subprocess.run(configure_cmd, check=True, env=env)

  build_cmd = [ninja_exe, "-C", build_dir, "-dkeepdepfile"] + build_targets
  subprocess.run(build_cmd, check=True, env=env)

  # The install target is broken, so we manually copy the headers.
  # The headers are located in the build directory in a gen/include folder.
  # The Skia build expects to find headers in subdirectories of the include path.
  generated_headers_src = os.path.join(build_dir, "gen", "include")
  generated_headers_dest = os.path.join(gen_dir, "include")

  if os.path.exists(generated_headers_dest):
    shutil.rmtree(generated_headers_dest)

  # Copy the contents of the 'dawn' and 'webgpu' directories into the destination.
  shutil.copytree(
      os.path.join(generated_headers_src, "dawn"),
      os.path.join(generated_headers_dest, "dawn"),
      dirs_exist_ok=True)
  shutil.copytree(
      os.path.join(generated_headers_src, "webgpu"),
      os.path.join(generated_headers_dest, "webgpu"),
      dirs_exist_ok=True)

  dependencies, object_files = discover_dependencies(build_dir, build_targets)
  write_depfile(output_path, depfile_path, dependencies)

  combine_into_library(args, output_path, build_dir, target_os, object_files)

if __name__ == "__main__":
  main()
