# 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): pkg_name = None if os == "win": pkg_name = "onnxruntime-win-" pkg_name += cpu_arch if ep == "cuda": pkg_name += "-cuda" elif ep == "tensorrt": pkg_name += "-tensorrt" elif os == "linux": pkg_name = "onnxruntime-linux-" pkg_name += cpu_arch if ep == "cuda": pkg_name += "-cuda" elif ep == "tensorrt": pkg_name += "-tensorrt" 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 def is_this_file_needed(ep, filename): 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): 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): 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): files_list.append( '' % cpu_arch ) for cpu_arch in ["x86_64", "arm64"]: if child.name == get_package_name("osx", cpu_arch, ep): 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( '' % cpu_arch ) for cpu_arch in ["x64", "aarch64"]: if child.name == get_package_name("linux", cpu_arch, ep): 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): files_list.append( '' % cpu_arch ) if child.name == "onnxruntime-android": for child_file in child.iterdir(): if child_file.suffix in [".aar"]: files_list.append('') if child.name == "onnxruntime-ios-xcframework": files_list.append('') 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", "tvm", "None"], help="The selected execution provider for this build.", ) parser.add_argument("--sdk_info", required=False, default="", type=str, help="dependency SDK information.") return parser.parse_args() def generate_id(line_list, package_name): line_list.append("" + package_name + "") def generate_version(line_list, package_version): line_list.append("" + package_version + "") def generate_authors(line_list, authors): line_list.append("" + authors + "") def generate_owners(line_list, owners): line_list.append("" + 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" 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 + "") def generate_copyright(line_list, copyright): line_list.append("" + copyright + "") def generate_tags(line_list, tags): line_list.append("" + tags + "") def generate_icon(line_list, icon_file): line_list.append("" + icon_file + "") def generate_license(line_list): line_list.append('LICENSE.txt') def generate_project_url(line_list, project_url): line_list.append("" + project_url + "") def generate_repo_url(line_list, repo_url, commit_id): line_list.append('') def generate_dependencies(xml_text, package_name, version): dml_dependency = '' if package_name == "Microsoft.AI.MachineLearning": xml_text.append("") # Support .Net Core xml_text.append('') xml_text.append(dml_dependency) xml_text.append("") # UAP10.0.16299, This is the earliest release of the OS that supports .NET Standard apps xml_text.append('') xml_text.append(dml_dependency) xml_text.append("") # Support Native C++ xml_text.append('') xml_text.append(dml_dependency) xml_text.append("") xml_text.append("") else: include_dml = package_name == "Microsoft.ML.OnnxRuntime.DirectML" xml_text.append("") # Support .Net Core xml_text.append('') xml_text.append('') if include_dml: xml_text.append(dml_dependency) xml_text.append("") # Support .Net Standard xml_text.append('') xml_text.append('') if include_dml: xml_text.append(dml_dependency) xml_text.append("") # Support .Net Framework xml_text.append('') xml_text.append('') if include_dml: xml_text.append(dml_dependency) xml_text.append("") if package_name == "Microsoft.ML.OnnxRuntime": # Support monoandroid11.0 xml_text.append('') xml_text.append('') xml_text.append("") # Support xamarinios10 xml_text.append('') xml_text.append('') xml_text.append("") # Support net6.0-android xml_text.append('') xml_text.append('') xml_text.append("") # Support net6.0-ios xml_text.append('') xml_text.append('') xml_text.append("") # Support net6.0-macos xml_text.append('') xml_text.append('') xml_text.append("") # Support Native C++ if include_dml: xml_text.append('') xml_text.append(dml_dependency) xml_text.append("") xml_text.append("") def get_env_var(key): return os.environ.get(key) def generate_release_notes(line_list, dependency_sdk_info): line_list.append("") 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("") def generate_metadata(line_list, args): metadata_list = [""] 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, "ONNX ONNX Runtime Machine Learning") 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_dependencies(metadata_list, args.package_name, args.package_version) generate_release_notes(metadata_list, args.sdk_info) metadata_list.append("") line_list += metadata_list def generate_files(line_list, args): files_list = [""] is_cpu_package = args.package_name in ["Microsoft.ML.OnnxRuntime", "Microsoft.ML.OnnxRuntime.OpenMP"] is_mklml_package = args.package_name == "Microsoft.ML.OnnxRuntime.MKLML" is_cuda_gpu_package = args.package_name == "Microsoft.ML.OnnxRuntime.Gpu" 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_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", "tvm": "tvm.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", "tvm_ep_shared_lib": "onnxruntime_providers_tvm.lib", "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", "tvm": "libtvm.so.0.5.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", "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 files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) if is_training_package: files_list.append( "' ) if args.execution_provider == "tvm": files_list.append( "' ) if args.execution_provider == "openvino": files_list.append( "' ) if args.execution_provider == "tensorrt": files_list.append( "' ) if args.execution_provider == "dnnl": files_list.append( "' ) if includes_directml: files_list.append( "' ) if includes_winml: # Add microsoft.ai.machinelearning headers files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) # Add custom operator headers mlop_path = "onnxruntime\\core\\providers\\dml\\dmlexecutionprovider\\inc\\mloperatorauthor.h" files_list.append( "' ) # Process microsoft.ai.machinelearning.winmd files_list.append( "' ) # Process microsoft.ai.machinelearning.experimental.winmd files_list.append( "' ) 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( "' ) 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( "' ) if args.package_name == "Microsoft.ML.OnnxRuntime.Snpe": files_list.append( "" ) files_list.append( "" ) 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: ep_list = ["tensorrt", "cuda", None] else: ep_list = [None] for ep in ep_list: generate_file_list_for_ep(nuget_artifacts_dir, ep, files_list, include_pdbs) is_ado_packaging_build = True else: # Code path for local dev build files_list.append( "" ) files_list.append( "" ) if include_pdbs and os.path.exists(os.path.join(args.native_build_path, "onnxruntime.pdb")): files_list.append( "" ) else: files_list.append( "' ) if includes_winml: # Process microsoft.ai.machinelearning import lib, dll, and pdb files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) # Process execution providers which are built as shared libs if args.execution_provider == "tensorrt" and not is_ado_packaging_build: files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) if args.execution_provider == "dnnl": files_list.append( "' ) files_list.append( "' ) if args.execution_provider == "tvm": files_list.append( "' ) files_list.append( "' ) tvm_build_path = os.path.join(args.ort_build_path, args.build_config, "_deps", "tvm-build") if is_windows(): files_list.append( "' ) else: # TODO(agladyshev): Add support for Linux. raise RuntimeError("Now only Windows is supported for TVM EP.") if args.execution_provider == "openvino": openvino_path = get_env_var("INTEL_OPENVINO_DIR") files_list.append( "' ) files_list.append( "' ) if is_windows(): if "2022" in openvino_path: dll_list_path = os.path.join(openvino_path, "runtime\\bin\\intel64\\Release\\") tbb_list_path = os.path.join(openvino_path, "runtime\\3rdparty\\tbb\\bin\\") else: dll_list_path = os.path.join( openvino_path, "deployment_tools\\inference_engine\\bin\\intel64\\Release\\" ) tbb_list_path = os.path.join(openvino_path, "deployment_tools\\inference_engine\\external\\tbb\\bin\\") ngraph_list_path = os.path.join(openvino_path, "deployment_tools\\ngraph\\lib\\") for ngraph_element in os.listdir(ngraph_list_path): if ngraph_element.endswith("dll"): files_list.append( "' ) for dll_element in os.listdir(dll_list_path): if dll_element.endswith("dll"): files_list.append( "' ) # plugins.xml files_list.append( "' ) # usb-ma2x8x.mvcmd # OpenVINO 2022.3 doesn't have usb-ma2x8x.mvcmd if "2022.3" not in openvino_path: files_list.append( "' ) for tbb_element in os.listdir(tbb_list_path): if tbb_element.endswith("dll"): files_list.append( "' ) if args.execution_provider == "cuda" or is_cuda_gpu_package and not is_ado_packaging_build: files_list.append( "' ) files_list.append( "' ) # 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( "" ) # Process mklml dependency if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["mklml"])): files_list.append( "" ) if is_linux() and os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["mklml_1"])): files_list.append( "" ) # Process libiomp5md dependency if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["openmp"])): files_list.append( "" ) # Process tvm dependency if os.path.exists(os.path.join(args.native_build_path, nuget_dependencies["tvm"])): files_list.append( "" ) # Some tools to be packaged in nightly build only, should not be released # These are copied to the runtimes folder for convenience of loading with the dlls 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( "" ) 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( "" ) # 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 = "build\\native" files_list.append("') # Process native targets files_list.append("') # Process rules files_list.append("') # 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("') files_list.append("') if is_cpu_package or is_cuda_gpu_package or is_dml_package or is_mklml_package or is_snpe_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("') if not is_snpe_package: files_list.append("') files_list.append("') # 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("') if not is_snpe_package: files_list.append("') files_list.append("') # Process xamarin targets files if args.package_name == "Microsoft.ML.OnnxRuntime": monoandroid_source_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "monoandroid11.0", "targets.xml", ) monoandroid_target_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "monoandroid11.0", args.package_name + ".targets", ) xamarinios_source_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "xamarinios10", "targets.xml" ) xamarinios_target_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "xamarinios10", args.package_name + ".targets", ) net6_android_source_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-android", "targets.xml", ) net6_android_target_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-android", args.package_name + ".targets", ) net6_ios_source_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-ios", "targets.xml" ) net6_ios_target_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-ios", args.package_name + ".targets", ) net6_macos_source_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-macos", "targets.xml" ) net6_macos_target_targets = os.path.join( args.sources_path, "csharp", "src", "Microsoft.ML.OnnxRuntime", "targets", "net6.0-macos", args.package_name + ".targets", ) os.system(copy_command + " " + monoandroid_source_targets + " " + monoandroid_target_targets) os.system(copy_command + " " + xamarinios_source_targets + " " + xamarinios_target_targets) os.system(copy_command + " " + net6_android_source_targets + " " + net6_android_target_targets) os.system(copy_command + " " + net6_ios_source_targets + " " + net6_ios_target_targets) os.system(copy_command + " " + net6_macos_source_targets + " " + net6_macos_target_targets) files_list.append("') files_list.append( "' ) files_list.append("') files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) files_list.append("') files_list.append( "' ) files_list.append("') files_list.append( "' ) # Process License, ThirdPartyNotices, Privacy files_list.append("') files_list.append( "' ) files_list.append( "' ) files_list.append( "' ) files_list.append("") line_list += files_list def generate_nuspec(args): lines = [''] lines.append("") generate_metadata(lines, args) generate_files(lines, args) lines.append("") 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" ): raise Exception( "On Linux platform nuget generation is supported only " "for cpu|cuda|dnnl|tensorrt|openvino 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 with open(os.path.join(args.native_build_path, "NativeNuget.nuspec"), "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())