# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import argparse
import os
import re
import sys
from pathlib import Path


# What does the names of our C API tarball/zip files looks like
# os: win, linux, osx
# ep: cuda, tensorrt, None
def get_package_name(os, cpu_arch, ep, is_training_package):
    pkg_name = "onnxruntime-training" if is_training_package else "onnxruntime"
    if os == "win":
        pkg_name += "-win-"
        pkg_name += cpu_arch
        if ep == "cuda":
            pkg_name += "-cuda"
        elif ep == "tensorrt":
            pkg_name += "-tensorrt"
        elif ep == "rocm":
            pkg_name += "-rocm"
    elif os == "linux":
        pkg_name += "-linux-"
        pkg_name += cpu_arch
        if ep == "cuda":
            pkg_name += "-cuda"
        elif ep == "tensorrt":
            pkg_name += "-tensorrt"
        elif ep == "rocm":
            pkg_name += "-rocm"
    elif os == "osx":
        pkg_name = "onnxruntime-osx-" + cpu_arch
    return pkg_name


# Currently we take onnxruntime_providers_cuda from CUDA build
# And onnxruntime, onnxruntime_providers_shared and
# onnxruntime_providers_tensorrt from tensorrt build
# cuda binaries are split out into the platform dependent packages Microsoft.ML.OnnxRuntime.{Linux|Windows}
# and not included in the base Microsoft.ML.OnnxRuntime.Gpu package
def is_this_file_needed(ep, filename, package_name):
    if package_name == "Microsoft.ML.OnnxRuntime.Gpu":
        return False
    return (ep != "cuda" or "cuda" in filename) and (ep != "tensorrt" or "cuda" not in filename)


# nuget_artifacts_dir: the directory with uncompressed C API tarball/zip files
# ep: cuda, tensorrt, None
# files_list: a list of xml string pieces to append
# This function has no return value. It updates files_list directly
def generate_file_list_for_ep(nuget_artifacts_dir, ep, files_list, include_pdbs, is_training_package, package_name):
    for child in nuget_artifacts_dir.iterdir():
        if not child.is_dir():
            continue

        for cpu_arch in ["x86", "x64", "arm", "arm64"]:
            if child.name == get_package_name("win", cpu_arch, ep, is_training_package):
                child = child / "lib"  # noqa: PLW2901
                for child_file in child.iterdir():
                    suffixes = [".dll", ".lib", ".pdb"] if include_pdbs else [".dll", ".lib"]
                    if (
                        child_file.suffix in suffixes
                        and is_this_file_needed(ep, child_file.name, package_name)
                        and package_name != "Microsoft.ML.OnnxRuntime.Gpu.Linux"
                    ):
                        files_list.append(
                            '<file src="' + str(child_file) + f'" target="runtimes/win-{cpu_arch}/native"/>'
                        )
        for cpu_arch in ["x86_64", "arm64"]:
            if child.name == get_package_name("osx", cpu_arch, ep, is_training_package):
                child = child / "lib"  # noqa: PLW2901
                if cpu_arch == "x86_64":
                    cpu_arch = "x64"  # noqa: PLW2901
                for child_file in child.iterdir():
                    # Check if the file has digits like onnxruntime.1.8.0.dylib. We can skip such things
                    is_versioned_dylib = re.match(r".*[\.\d+]+\.dylib$", child_file.name)
                    if child_file.is_file() and child_file.suffix == ".dylib" and not is_versioned_dylib:
                        files_list.append(
                            '<file src="' + str(child_file) + f'" target="runtimes/osx-{cpu_arch}/native"/>'
                        )
        for cpu_arch in ["x64", "aarch64"]:
            if child.name == get_package_name("linux", cpu_arch, ep, is_training_package):
                child = child / "lib"  # noqa: PLW2901
                if cpu_arch == "x86_64":
                    cpu_arch = "x64"  # noqa: PLW2901
                elif cpu_arch == "aarch64":
                    cpu_arch = "arm64"  # noqa: PLW2901
                for child_file in child.iterdir():
                    if not child_file.is_file():
                        continue
                    if (
                        child_file.suffix == ".so"
                        and is_this_file_needed(ep, child_file.name, package_name)
                        and package_name != "Microsoft.ML.OnnxRuntime.Gpu.Windows"
                    ):
                        files_list.append(
                            '<file src="' + str(child_file) + f'" target="runtimes/linux-{cpu_arch}/native"/>'
                        )

        if child.name == "onnxruntime-android" or child.name == "onnxruntime-training-android":
            for child_file in child.iterdir():
                if child_file.suffix in [".aar"]:
                    files_list.append('<file src="' + str(child_file) + '" target="runtimes/android/native"/>')

        if child.name == "onnxruntime-ios":
            for child_file in child.iterdir():
                if child_file.suffix in [".zip"]:
                    files_list.append('<file src="' + str(child_file) + '" target="runtimes/ios/native"/>')


def parse_arguments():
    parser = argparse.ArgumentParser(
        description="ONNX Runtime create nuget spec script (for hosting native shared library artifacts)",
        usage="",
    )
    # Main arguments
    parser.add_argument("--package_name", required=True, help="ORT package name. Eg: Microsoft.ML.OnnxRuntime.Gpu")
    parser.add_argument("--package_version", required=True, help="ORT package version. Eg: 1.0.0")
    parser.add_argument("--target_architecture", required=True, help="Eg: x64")
    parser.add_argument("--build_config", required=True, help="Eg: RelWithDebInfo")
    parser.add_argument("--ort_build_path", required=True, help="ORT build directory.")
    parser.add_argument("--native_build_path", required=True, help="Native build output directory.")
    parser.add_argument("--packages_path", required=True, help="Nuget packages output directory.")
    parser.add_argument("--sources_path", required=True, help="OnnxRuntime source code root.")
    parser.add_argument("--commit_id", required=True, help="The last commit id included in this package.")
    parser.add_argument(
        "--is_release_build",
        required=False,
        default=None,
        type=str,
        help="Flag indicating if the build is a release build. Accepted values: true/false.",
    )
    parser.add_argument(
        "--execution_provider",
        required=False,
        default="None",
        type=str,
        choices=["cuda", "dnnl", "openvino", "tensorrt", "snpe", "qnn", "None"],
        help="The selected execution provider for this build.",
    )
    parser.add_argument("--sdk_info", required=False, default="", type=str, help="dependency SDK information.")
    parser.add_argument(
        "--nuspec_name", required=False, default="NativeNuget.nuspec", type=str, help="nuget spec name."
    )

    return parser.parse_args()


def generate_id(line_list, package_name):
    line_list.append("<id>" + package_name + "</id>")


def generate_version(line_list, package_version):
    line_list.append("<version>" + package_version + "</version>")


def generate_authors(line_list, authors):
    line_list.append("<authors>" + authors + "</authors>")


def generate_owners(line_list, owners):
    line_list.append("<owners>" + owners + "</owners>")


def generate_description(line_list, package_name):
    description = ""

    if package_name == "Microsoft.AI.MachineLearning":
        description = "This package contains Windows ML binaries."
    elif "Microsoft.ML.OnnxRuntime.Training" in package_name:  # This is a Microsoft.ML.OnnxRuntime.Training.* package
        description = (
            "The onnxruntime-training native shared library artifacts are designed to efficiently train and infer "
            + "a wide range of ONNX models on edge devices, such as client machines, gaming consoles, and other "
            + "portable devices with a focus on minimizing resource usage and maximizing accuracy."
            + "See https://github.com/microsoft/onnxruntime-training-examples/tree/master/on_device_training for "
            + "more details."
        )
    elif "Microsoft.ML.OnnxRuntime.Gpu.Linux" in package_name:
        description = "This package contains Linux native shared library artifacts for ONNX Runtime with CUDA."
    elif "Microsoft.ML.OnnxRuntime.Gpu.Windows" in package_name:
        description = "This package contains Windows native shared library artifacts for ONNX Runtime with CUDA."
    elif "Intel.ML.OnnxRuntime" in package_name:
        description = "This package contains native shared library artifacts for ONNX Runtime with OpenVINO."
    elif "Microsoft.ML.OnnxRuntime" in package_name:  # This is a Microsoft.ML.OnnxRuntime.* package
        description = (
            "This package contains native shared library artifacts for all supported platforms of ONNX Runtime."
        )
    line_list.append("<description>" + description + "</description>")


def generate_copyright(line_list, copyright):
    line_list.append("<copyright>" + copyright + "</copyright>")


def generate_tags(line_list, tags):
    line_list.append("<tags>" + tags + "</tags>")


def generate_icon(line_list, icon_file):
    line_list.append("<icon>" + icon_file + "</icon>")


def generate_license(line_list):
    line_list.append('<license type="file">LICENSE</license>')


def generate_project_url(line_list, project_url):
    line_list.append("<projectUrl>" + project_url + "</projectUrl>")


def generate_repo_url(line_list, repo_url, commit_id):
    line_list.append('<repository type="git" url="' + repo_url + '"' + ' commit="' + commit_id + '" />')


def generate_readme(line_list):
    line_list.append("<readme>README.md</readme>")


def add_common_dependencies(xml_text, package_name, version):
    xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Managed"' + ' version="' + version + '"/>')
    if package_name == "Microsoft.ML.OnnxRuntime.Gpu":
        xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Gpu.Windows"' + ' version="' + version + '"/>')
        xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Gpu.Linux"' + ' version="' + version + '"/>')


def generate_dependencies(xml_text, package_name, version):
    dml_dependency = '<dependency id="Microsoft.AI.DirectML" version="1.15.4"/>'

    if package_name == "Microsoft.AI.MachineLearning":
        xml_text.append("<dependencies>")
        # Support .Net Core
        xml_text.append('<group targetFramework="net5.0">')
        xml_text.append(dml_dependency)
        xml_text.append("</group>")
        # UAP10.0.16299, This is the earliest release of the OS that supports .NET Standard apps
        xml_text.append('<group targetFramework="UAP10.0.16299">')
        xml_text.append(dml_dependency)
        xml_text.append("</group>")
        # Support Native C++
        xml_text.append('<group targetFramework="native">')
        xml_text.append(dml_dependency)
        xml_text.append("</group>")
        xml_text.append("</dependencies>")
    else:
        include_dml = package_name == "Microsoft.ML.OnnxRuntime.DirectML"

        xml_text.append("<dependencies>")

        # Support .Net Core
        xml_text.append('<group targetFramework="NETCOREAPP">')
        add_common_dependencies(xml_text, package_name, version)
        if include_dml:
            xml_text.append(dml_dependency)
        xml_text.append("</group>")
        # Support .Net Standard
        xml_text.append('<group targetFramework="NETSTANDARD">')
        add_common_dependencies(xml_text, package_name, version)
        if include_dml:
            xml_text.append(dml_dependency)
        xml_text.append("</group>")
        # Support .Net Framework
        xml_text.append('<group targetFramework="NETFRAMEWORK">')
        add_common_dependencies(xml_text, package_name, version)
        if include_dml:
            xml_text.append(dml_dependency)
        xml_text.append("</group>")
        if package_name == "Microsoft.ML.OnnxRuntime":
            # Support net8.0-android
            xml_text.append('<group targetFramework="net8.0-android31.0">')
            xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Managed"' + ' version="' + version + '"/>')
            xml_text.append("</group>")
            # Support net8.0-ios
            xml_text.append('<group targetFramework="net8.0-ios15.4">')
            xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Managed"' + ' version="' + version + '"/>')
            xml_text.append("</group>")
            # Support net8.0-maccatalyst
            xml_text.append('<group targetFramework="net8.0-maccatalyst14.0">')
            xml_text.append('<dependency id="Microsoft.ML.OnnxRuntime.Managed"' + ' version="' + version + '"/>')
            xml_text.append("</group>")
        # Support Native C++
        if include_dml:
            xml_text.append('<group targetFramework="native">')
            xml_text.append(dml_dependency)
            xml_text.append("</group>")

        xml_text.append("</dependencies>")


def get_env_var(key):
    return os.environ.get(key)


def generate_release_notes(line_list, dependency_sdk_info):
    line_list.append("<releaseNotes>")
    line_list.append("Release Def:")

    branch = get_env_var("BUILD_SOURCEBRANCH")
    line_list.append("\t" + "Branch: " + (branch if branch is not None else ""))

    version = get_env_var("BUILD_SOURCEVERSION")
    line_list.append("\t" + "Commit: " + (version if version is not None else ""))

    build_id = get_env_var("BUILD_BUILDID")
    line_list.append(
        "\t"
        + "Build: https://aiinfra.visualstudio.com/Lotus/_build/results?buildId="
        + (build_id if build_id is not None else "")
    )

    if dependency_sdk_info:
        line_list.append("Dependency SDK: " + dependency_sdk_info)

    line_list.append("</releaseNotes>")


def generate_metadata(line_list, args):
    tags = "native ONNX Runtime ONNXRuntime Machine Learning MachineLearning"
    if "Microsoft.ML.OnnxRuntime.Training." in args.package_name:
        tags.append(" ONNXRuntime-Training Learning-on-The-Edge On-Device-Training On-Device Training")

    metadata_list = ["<metadata>"]
    generate_id(metadata_list, args.package_name)
    generate_version(metadata_list, args.package_version)
    generate_authors(metadata_list, "Microsoft")
    generate_owners(metadata_list, "Microsoft")
    generate_description(metadata_list, args.package_name)
    generate_copyright(metadata_list, "\xc2\xa9 " + "Microsoft Corporation. All rights reserved.")
    generate_tags(metadata_list, tags)
    generate_icon(metadata_list, "ORT_icon_for_light_bg.png")
    generate_license(metadata_list)
    generate_project_url(metadata_list, "https://github.com/Microsoft/onnxruntime")
    generate_repo_url(metadata_list, "https://github.com/Microsoft/onnxruntime.git", args.commit_id)
    generate_readme(metadata_list)
    generate_dependencies(metadata_list, args.package_name, args.package_version)
    generate_release_notes(metadata_list, args.sdk_info)
    metadata_list.append("</metadata>")

    line_list += metadata_list


def generate_files(line_list, args):
    files_list = ["<files>"]

    is_cpu_package = args.package_name in [
        "Microsoft.ML.OnnxRuntime",
        "Microsoft.ML.OnnxRuntime.OpenMP",
        "Microsoft.ML.OnnxRuntime.Training",
    ]
    is_mklml_package = args.package_name == "Microsoft.ML.OnnxRuntime.MKLML"
    is_cuda_gpu_package = args.package_name == "Microsoft.ML.OnnxRuntime.Gpu"
    is_cuda_gpu_win_sub_package = args.package_name == "Microsoft.ML.OnnxRuntime.Gpu.Windows"
    is_cuda_gpu_linux_sub_package = args.package_name == "Microsoft.ML.OnnxRuntime.Gpu.Linux"
    is_rocm_gpu_package = args.package_name == "Microsoft.ML.OnnxRuntime.ROCm"
    is_dml_package = args.package_name == "Microsoft.ML.OnnxRuntime.DirectML"
    is_windowsai_package = args.package_name == "Microsoft.AI.MachineLearning"
    is_snpe_package = args.package_name == "Microsoft.ML.OnnxRuntime.Snpe"
    is_qnn_package = args.package_name == "Microsoft.ML.OnnxRuntime.QNN"
    is_training_package = args.package_name in [
        "Microsoft.ML.OnnxRuntime.Training",
        "Microsoft.ML.OnnxRuntime.Training.Gpu",
    ]

    includes_winml = is_windowsai_package
    includes_directml = (is_dml_package or is_windowsai_package) and (
        args.target_architecture == "x64" or args.target_architecture == "x86"
    )

    is_windows_build = is_windows()

    nuget_dependencies = {}

    if is_windows_build:
        nuget_dependencies = {
            "mklml": "mklml.dll",
            "openmp": "libiomp5md.dll",
            "dnnl": "dnnl.dll",
            "providers_shared_lib": "onnxruntime_providers_shared.dll",
            "dnnl_ep_shared_lib": "onnxruntime_providers_dnnl.dll",
            "tensorrt_ep_shared_lib": "onnxruntime_providers_tensorrt.dll",
            "openvino_ep_shared_lib": "onnxruntime_providers_openvino.dll",
            "cuda_ep_shared_lib": "onnxruntime_providers_cuda.dll",
            "qnn_ep_shared_lib": "onnxruntime_providers_qnn.dll",
            "onnxruntime_perf_test": "onnxruntime_perf_test.exe",
            "onnx_test_runner": "onnx_test_runner.exe",
        }

        copy_command = "copy"
        runtimes_target = '" target="runtimes\\win-'
    else:
        nuget_dependencies = {
            "mklml": "libmklml_intel.so",
            "mklml_1": "libmklml_gnu.so",
            "openmp": "libiomp5.so",
            "dnnl": "libdnnl.so.1",
            "providers_shared_lib": "libonnxruntime_providers_shared.so",
            "dnnl_ep_shared_lib": "libonnxruntime_providers_dnnl.so",
            "tensorrt_ep_shared_lib": "libonnxruntime_providers_tensorrt.so",
            "openvino_ep_shared_lib": "libonnxruntime_providers_openvino.so",
            "cuda_ep_shared_lib": "libonnxruntime_providers_cuda.so",
            "rocm_ep_shared_lib": "libonnxruntime_providers_rocm.so",
            "onnxruntime_perf_test": "onnxruntime_perf_test",
            "onnx_test_runner": "onnx_test_runner",
        }

        copy_command = "cp"
        runtimes_target = '" target="runtimes\\linux-'

    if is_windowsai_package:
        runtimes_native_folder = "_native"
    else:
        runtimes_native_folder = "native"

    runtimes = f'{runtimes_target}{args.target_architecture}\\{runtimes_native_folder}"'

    # Process headers
    build_dir = "buildTransitive" if "Gpu" in args.package_name else "build"
    include_dir = f"{build_dir}\\native\\include"

    # Sub.Gpu packages do not include the onnxruntime headers
    if args.package_name != "Microsoft.ML.OnnxRuntime.Gpu":
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "include\\onnxruntime\\core\\session\\onnxruntime_*.h")
            + '" target="'
            + include_dir
            + '" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "include\\onnxruntime\\core\\framework\\provider_options.h")
            + '" target="'
            + include_dir
            + '" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "include\\onnxruntime\\core\\providers\\cpu\\cpu_provider_factory.h")
            + '" target="'
            + include_dir
            + '" />'
        )

    if is_training_package:
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(
                args.sources_path, "orttraining\\orttraining\\training_api\\include\\onnxruntime_training_*.h"
            )
            + '" target="build\\native\\include" />'
        )

    if args.execution_provider == "openvino":
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(
                args.sources_path, "include\\onnxruntime\\core\\providers\\openvino\\openvino_provider_factory.h"
            )
            + '" target="build\\native\\include" />'
        )

    if args.execution_provider == "tensorrt":
        files_list.append("<file src=" + '"' + '" target="build\\native\\include" />')

    if args.execution_provider == "dnnl":
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "include\\onnxruntime\\core\\providers\\dnnl\\dnnl_provider_options.h")
            + '" target="build\\native\\include" />'
        )

    if includes_directml:
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "include\\onnxruntime\\core\\providers\\dml\\dml_provider_factory.h")
            + '" target="build\\native\\include" />'
        )

    if includes_winml:
        # Add microsoft.ai.machinelearning headers
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.ort_build_path, args.build_config, "microsoft.ai.machinelearning.h")
            + '" target="build\\native\\include\\abi\\Microsoft.AI.MachineLearning.h" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.sources_path, "winml\\api\\dualapipartitionattribute.h")
            + '" target="build\\native\\include\\abi\\dualapipartitionattribute.h" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.ort_build_path, args.build_config, "microsoft.ai.machinelearning.native.h")
            + '" target="build\\native\\include\\Microsoft.AI.MachineLearning.Native.h" />'
        )
        # Add custom operator headers
        mlop_path = "onnxruntime\\core\\providers\\dml\\dmlexecutionprovider\\inc\\mloperatorauthor.h"
        files_list.append(
            "<file src=" + '"' + os.path.join(args.sources_path, mlop_path) + '" target="build\\native\\include" />'
        )
        # Process microsoft.ai.machinelearning.winmd
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.ort_build_path, args.build_config, "microsoft.ai.machinelearning.winmd")
            + '" target="winmds\\Microsoft.AI.MachineLearning.winmd" />'
        )
        # Process microsoft.ai.machinelearning.experimental.winmd
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.ort_build_path, args.build_config, "microsoft.ai.machinelearning.experimental.winmd")
            + '" target="winmds\\Microsoft.AI.MachineLearning.Experimental.winmd" />'
        )
        if args.target_architecture == "x64":
            interop_dll_path = "Microsoft.AI.MachineLearning.Interop\\net5.0-windows10.0.17763.0"
            interop_dll = interop_dll_path + "\\Microsoft.AI.MachineLearning.Interop.dll"
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, interop_dll)
                + '" target="lib\\net5.0\\Microsoft.AI.MachineLearning.Interop.dll" />'
            )
            interop_pdb_path = "Microsoft.AI.MachineLearning.Interop\\net5.0-windows10.0.17763.0"
            interop_pdb = interop_pdb_path + "\\Microsoft.AI.MachineLearning.Interop.pdb"
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, interop_pdb)
                + '" target="lib\\net5.0\\Microsoft.AI.MachineLearning.Interop.pdb" />'
            )

    if args.package_name == "Microsoft.ML.OnnxRuntime.Snpe" or args.package_name == "Microsoft.ML.OnnxRuntime.QNN":
        files_list.append(
            "<file src=" + '"' + os.path.join(args.native_build_path, "onnx_test_runner.exe") + runtimes + " />"
        )
        files_list.append(
            "<file src=" + '"' + os.path.join(args.native_build_path, "onnxruntime_perf_test.exe") + runtimes + " />"
        )

    if is_qnn_package:
        files_list.append("<file src=" + '"' + os.path.join(args.native_build_path, "QnnCpu.dll") + runtimes + " />")
        files_list.append("<file src=" + '"' + os.path.join(args.native_build_path, "QnnHtp.dll") + runtimes + " />")
        files_list.append("<file src=" + '"' + os.path.join(args.native_build_path, "QnnSaver.dll") + runtimes + " />")
        if args.target_architecture != "x64":
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "QnnSystem.dll") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "QnnHtpPrepare.dll") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "QnnHtpV73Stub.dll") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "libQnnHtpV73Skel.so") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "libqnnhtpv73.cat") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "QnnHtpV68Stub.dll") + runtimes + " />"
            )
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, "libQnnHtpV68Skel.so") + runtimes + " />"
            )

    is_ado_packaging_build = False
    # Process runtimes
    # Process onnxruntime import lib, dll, and pdb
    # for Snpe android build
    if is_windows_build:
        nuget_artifacts_dir = Path(args.native_build_path) / "nuget-artifacts"
        # the winml package includes pdbs. for other packages exclude them.
        include_pdbs = includes_winml
        if nuget_artifacts_dir.exists():
            # Code path for ADO build pipeline, the files under 'nuget-artifacts' are
            # downloaded from other build jobs
            if is_cuda_gpu_package or is_cuda_gpu_win_sub_package or is_cuda_gpu_linux_sub_package:
                ep_list = ["tensorrt", "cuda", None]
            elif is_rocm_gpu_package:
                ep_list = ["rocm", None]
            else:
                ep_list = [None]
            for ep in ep_list:
                generate_file_list_for_ep(
                    nuget_artifacts_dir, ep, files_list, include_pdbs, is_training_package, args.package_name
                )
            is_ado_packaging_build = True
        else:
            # Code path for local dev build
            # for local dev build, gpu linux package is also generated for compatibility though it is not used
            if not is_cuda_gpu_linux_sub_package:
                files_list.append(
                    "<file src=" + '"' + os.path.join(args.native_build_path, "onnxruntime.lib") + runtimes + " />"
                )
                files_list.append(
                    "<file src=" + '"' + os.path.join(args.native_build_path, "onnxruntime.dll") + runtimes + " />"
                )
                if include_pdbs and os.path.exists(os.path.join(args.native_build_path, "onnxruntime.pdb")):
                    files_list.append(
                        "<file src=" + '"' + os.path.join(args.native_build_path, "onnxruntime.pdb") + runtimes + " />"
                    )
    else:
        ort_so = os.path.join(args.native_build_path, "libonnxruntime.so")
        if os.path.exists(ort_so):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, "libonnxruntime.so")
                + '" target="runtimes\\linux-'
                + args.target_architecture
                + '\\native" />'
            )

    if includes_winml:
        # Process microsoft.ai.machinelearning import lib, dll, and pdb
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, "microsoft.ai.machinelearning.lib")
            + runtimes_target
            + args.target_architecture
            + "\\_native"
            + '\\Microsoft.AI.MachineLearning.lib" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, "microsoft.ai.machinelearning.dll")
            + runtimes_target
            + args.target_architecture
            + "\\_native"
            + '\\Microsoft.AI.MachineLearning.dll" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, "microsoft.ai.machinelearning.pdb")
            + runtimes_target
            + args.target_architecture
            + "\\_native"
            + '\\Microsoft.AI.MachineLearning.pdb" />'
        )
    # Process execution providers which are built as shared libs
    if args.execution_provider == "tensorrt" and not is_ado_packaging_build:
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["cuda_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["tensorrt_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

    if args.execution_provider == "dnnl":
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["dnnl_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

    if args.execution_provider == "rocm" or (is_rocm_gpu_package and not is_ado_packaging_build):
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["rocm_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

    if args.execution_provider == "openvino":
        openvino_path = get_env_var("INTEL_OPENVINO_DIR")
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["openvino_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

        if is_windows():
            dll_list_path = os.path.join(openvino_path, "runtime\\bin\\intel64\\Release\\")
            tbb_list_path = os.path.join(openvino_path, "runtime\\3rdparty\\tbb\\bin\\")
            for dll_element in os.listdir(dll_list_path):
                if dll_element.endswith("dll"):
                    files_list.append(
                        "<file src="
                        + '"'
                        + os.path.join(dll_list_path, dll_element)
                        + runtimes_target
                        + args.target_architecture
                        + '\\native" />'
                    )
            for tbb_element in os.listdir(tbb_list_path):
                if tbb_element.endswith("dll"):
                    files_list.append(
                        "<file src="
                        + '"'
                        + os.path.join(tbb_list_path, tbb_element)
                        + runtimes_target
                        + args.target_architecture
                        + '\\native" />'
                    )

    if args.execution_provider == "cuda" or (is_cuda_gpu_win_sub_package and not is_ado_packaging_build):
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["cuda_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

    if args.execution_provider == "qnn" or (is_qnn_package and not is_ado_packaging_build):
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["providers_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, nuget_dependencies["qnn_ep_shared_lib"])
            + runtimes_target
            + args.target_architecture
            + '\\native" />'
        )

    # process all other library dependencies
    if is_cpu_package or is_cuda_gpu_package or is_dml_package or is_mklml_package:
        # Process dnnl dependency
        if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["dnnl"])):
            files_list.append(
                "<file src=" + '"' + os.path.join(args.native_build_path, nuget_dependencies["dnnl"]) + runtimes + " />"
            )

        # Process mklml dependency
        if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["mklml"])):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, nuget_dependencies["mklml"])
                + runtimes
                + " />"
            )

        if is_linux() and os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["mklml_1"])):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, nuget_dependencies["mklml_1"])
                + runtimes
                + " />"
            )

        # Process libiomp5md dependency
        if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["openmp"])):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, nuget_dependencies["openmp"])
                + runtimes
                + " />"
            )

        # Some tools to be packaged in nightly debug build only, should not be released
        # These are copied to the runtimes folder for convenience of loading with the dlls
        # NOTE: nuget gives a spurious error on linux if these aren't in a separate directory to the library so
        #       we add them to a tools folder for that reason.
        if (
            args.is_release_build.lower() != "true"
            and args.target_architecture == "x64"
            and os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["onnxruntime_perf_test"]))
        ):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, nuget_dependencies["onnxruntime_perf_test"])
                + runtimes[:-1]
                + "\\tools\\"
                + nuget_dependencies["onnxruntime_perf_test"]
                + '"'
                + " />"
            )

        if (
            args.is_release_build.lower() != "true"
            and args.target_architecture == "x64"
            and os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["onnx_test_runner"]))
        ):
            files_list.append(
                "<file src="
                + '"'
                + os.path.join(args.native_build_path, nuget_dependencies["onnx_test_runner"])
                + runtimes[:-1]
                + "\\tools\\"
                + nuget_dependencies["onnx_test_runner"]
                + '"'
                + " />"
            )

    # Process props and targets files
    if is_windowsai_package:
        windowsai_src = "Microsoft.AI.MachineLearning"
        windowsai_props = "Microsoft.AI.MachineLearning.props"
        windowsai_targets = "Microsoft.AI.MachineLearning.targets"
        windowsai_native_props = os.path.join(args.sources_path, "csharp", "src", windowsai_src, windowsai_props)
        windowsai_rules = "Microsoft.AI.MachineLearning.Rules.Project.xml"
        windowsai_native_rules = os.path.join(args.sources_path, "csharp", "src", windowsai_src, windowsai_rules)
        windowsai_native_targets = os.path.join(args.sources_path, "csharp", "src", windowsai_src, windowsai_targets)
        build = f"{build_dir}\\native"
        files_list.append("<file src=" + '"' + windowsai_native_props + '" target="' + build + '" />')
        # Process native targets
        files_list.append("<file src=" + '"' + windowsai_native_targets + '" target="' + build + '" />')
        # Process rules
        files_list.append("<file src=" + '"' + windowsai_native_rules + '" target="' + build + '" />')
        # Process .net5.0 targets
        if args.target_architecture == "x64":
            interop_src = "Microsoft.AI.MachineLearning.Interop"
            interop_props = "Microsoft.AI.MachineLearning.props"
            interop_targets = "Microsoft.AI.MachineLearning.targets"
            windowsai_net50_props = os.path.join(args.sources_path, "csharp", "src", interop_src, interop_props)
            windowsai_net50_targets = os.path.join(args.sources_path, "csharp", "src", interop_src, interop_targets)
            files_list.append("<file src=" + '"' + windowsai_net50_props + '" target="build\\net5.0" />')
            files_list.append("<file src=" + '"' + windowsai_net50_targets + '" target="build\\net5.0" />')

    if (
        is_cpu_package
        or is_cuda_gpu_package
        or is_cuda_gpu_linux_sub_package
        or is_cuda_gpu_win_sub_package
        or is_rocm_gpu_package
        or is_dml_package
        or is_mklml_package
        or is_snpe_package
        or is_qnn_package
    ):
        # Process props file
        source_props = os.path.join(
            args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "netstandard", "props.xml"
        )
        target_props = os.path.join(
            args.sources_path,
            "csharp",
            "src",
            "Microsoft.ML.OnnxRuntime",
            "targets",
            "netstandard",
            args.package_name + ".props",
        )
        os.system(copy_command + " " + source_props + " " + target_props)
        files_list.append("<file src=" + '"' + target_props + '" target="' + build_dir + '\\native" />')
        if not is_snpe_package and not is_qnn_package:
            files_list.append("<file src=" + '"' + target_props + '" target="' + build_dir + '\\netstandard2.0"  />')
            files_list.append("<file src=" + '"' + target_props + '" target="' + build_dir + '\\netstandard2.1"  />')

        # Process targets file
        source_targets = os.path.join(
            args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "netstandard", "targets.xml"
        )
        target_targets = os.path.join(
            args.sources_path,
            "csharp",
            "src",
            "Microsoft.ML.OnnxRuntime",
            "targets",
            "netstandard",
            args.package_name + ".targets",
        )
        os.system(copy_command + " " + source_targets + " " + target_targets)
        files_list.append("<file src=" + '"' + target_targets + '" target="' + build_dir + '\\native"  />')
        if not is_snpe_package and not is_qnn_package:
            files_list.append("<file src=" + '"' + target_targets + '" target="' + build_dir + '\\netstandard2.0" />')
            files_list.append("<file src=" + '"' + target_targets + '" target="' + build_dir + '\\netstandard2.1"  />')

        # Process xamarin targets files
        if args.package_name == "Microsoft.ML.OnnxRuntime":
            net8_android_source_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-android",
                "targets.xml",
            )
            net8_android_target_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-android",
                args.package_name + ".targets",
            )

            net8_ios_source_targets = os.path.join(
                args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net8.0-ios", "targets.xml"
            )
            net8_ios_target_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-ios",
                args.package_name + ".targets",
            )

            net8_maccatalyst_source_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-maccatalyst",
                "_._",
            )

            net8_maccatalyst_target_targets = os.path.join(
                args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net8.0-maccatalyst", "_._"
            )

            os.system(copy_command + " " + net8_android_source_targets + " " + net8_android_target_targets)
            os.system(copy_command + " " + net8_ios_source_targets + " " + net8_ios_target_targets)
            os.system(copy_command + " " + net8_maccatalyst_source_targets + " " + net8_maccatalyst_target_targets)

            files_list.append(
                "<file src=" + '"' + net8_android_target_targets + '" target="build\\net8.0-android31.0" />'
            )
            files_list.append(
                "<file src=" + '"' + net8_android_target_targets + '" target="buildTransitive\\net8.0-android31.0" />'
            )

            files_list.append("<file src=" + '"' + net8_ios_target_targets + '" target="build\\net8.0-ios15.4" />')
            files_list.append(
                "<file src=" + '"' + net8_ios_target_targets + '" target="buildTransitive\\net8.0-ios15.4" />'
            )
            files_list.append(
                "<file src=" + '"' + net8_maccatalyst_target_targets + '" target="build\\net8.0-maccatalyst14.0" />'
            )
            files_list.append(
                "<file src="
                + '"'
                + net8_maccatalyst_target_targets
                + '" target="buildTransitive\\net8.0-maccatalyst14.0" />'
            )

        # Process Training specific targets and props
        if args.package_name == "Microsoft.ML.OnnxRuntime.Training":
            net8_android_source_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-android",
                "targets.xml",
            )
            net8_android_target_targets = os.path.join(
                args.sources_path,
                "csharp",
                "src",
                "Microsoft.ML.OnnxRuntime",
                "targets",
                "net8.0-android",
                args.package_name + ".targets",
            )

            os.system(copy_command + " " + net8_android_source_targets + " " + net8_android_target_targets)
            files_list.append(
                "<file src=" + '"' + net8_android_target_targets + '" target="build\\net8.0-android31.0" />'
            )
            files_list.append(
                "<file src=" + '"' + net8_android_target_targets + '" target="buildTransitive\\net8.0-android31.0" />'
            )

    # README
    files_list.append(
        "<file src=" + '"' + os.path.join(args.sources_path, "tools/nuget/nupkg.README.md") + '" target="README.md" />'
    )

    # Process License, ThirdPartyNotices, Privacy
    files_list.append("<file src=" + '"' + os.path.join(args.sources_path, "LICENSE") + '" target="LICENSE" />')
    files_list.append(
        "<file src="
        + '"'
        + os.path.join(args.sources_path, "ThirdPartyNotices.txt")
        + '" target="ThirdPartyNotices.txt" />'
    )
    files_list.append(
        "<file src=" + '"' + os.path.join(args.sources_path, "docs", "Privacy.md") + '" target="Privacy.md" />'
    )
    files_list.append(
        "<file src="
        + '"'
        + os.path.join(args.sources_path, "ORT_icon_for_light_bg.png")
        + '" target="ORT_icon_for_light_bg.png" />'
    )
    if is_qnn_package:
        files_list.append(
            "<file src="
            + '"'
            + os.path.join(args.native_build_path, "Qualcomm AI Hub Proprietary License.pdf")
            + '" target="Qualcomm AI Hub Proprietary License.pdf" />'
        )
    files_list.append("</files>")

    line_list += files_list


def generate_nuspec(args):
    lines = ['<?xml version="1.0"?>']
    lines.append("<package>")
    generate_metadata(lines, args)
    generate_files(lines, args)
    lines.append("</package>")
    return lines


def is_windows():
    return sys.platform.startswith("win")


def is_linux():
    return sys.platform.startswith("linux")


def is_macos():
    return sys.platform.startswith("darwin")


def validate_platform():
    if not (is_windows() or is_linux() or is_macos()):
        raise Exception("Native Nuget generation is currently supported only on Windows, Linux, and MacOS")


def validate_execution_provider(execution_provider):
    if is_linux():
        if not (
            execution_provider == "None"
            or execution_provider == "dnnl"
            or execution_provider == "cuda"
            or execution_provider == "tensorrt"
            or execution_provider == "openvino"
            or execution_provider == "rocm"
        ):
            raise Exception(
                "On Linux platform nuget generation is supported only "
                "for cpu|cuda|dnnl|tensorrt|openvino|rocm execution providers."
            )


def main():
    # Parse arguments
    args = parse_arguments()

    validate_platform()

    validate_execution_provider(args.execution_provider)

    if args.is_release_build.lower() != "true" and args.is_release_build.lower() != "false":
        raise Exception("Only valid options for IsReleaseBuild are: true and false")

    # Generate nuspec
    lines = generate_nuspec(args)

    # Create the nuspec needed to generate the Nuget
    print(f"nuspec_name: {args.nuspec_name}")
    with open(os.path.join(args.native_build_path, args.nuspec_name), "w") as f:
        for line in lines:
            # Uncomment the printing of the line if you need to debug what's produced on a CI machine
            # print(line)
            f.write(line)
            f.write("\n")


if __name__ == "__main__":
    sys.exit(main())
