// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using Flax.Build; using Flax.Build.Platforms; using Flax.Build.Projects.VisualStudio; using Flax.Deploy; namespace Flax.Deps { /// /// Represents a single dependency package required by the engine to be pre-build. /// abstract class Dependency { /// /// The options. /// public class BuildOptions { /// /// The intermediate folder for the deps to be build inside it. /// public string IntermediateFolder; /// /// The output folder for the platform dependencies. /// public string PlatformsFolder; /// /// The ThirdParty header files folder for the deps. /// public string ThirdPartyFolder; /// /// The target platforms to build dependency for (contains only platforms supported by the dependency itself). /// public TargetPlatform[] Platforms; } /// /// Gets the build platform. /// protected static TargetPlatform BuildPlatform => Platform.BuildPlatform.Target; /// /// Gets the platforms list supported by this dependency to build on the current build platform (based on ). /// public abstract TargetPlatform[] Platforms { get; } /// /// True if build dependency by default, otherwise only when explicitly specified via command line. /// public virtual bool BuildByDefault => true; /// /// Builds the dependency package using the specified options. /// /// The options. public abstract void Build(BuildOptions options); /// /// Gets the dependency third-party packages binaries folder. /// /// The options. /// The target platform. /// The target architecture. /// The absolute path to the deps folder for the given platform and architecture configuration. public static string GetThirdPartyFolder(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture) { return Path.Combine(options.PlatformsFolder, platform.ToString(), "Binaries", "ThirdParty", architecture.ToString()); } /// /// Gets the dependency packages binaries folder. /// /// The options. /// The target platform. /// The absolute path to the deps folder for the given platform and architecture configuration. public static string GetBinariesFolder(BuildOptions options, TargetPlatform platform) { return Path.Combine(options.PlatformsFolder, platform.ToString(), "Binaries"); } /// /// Setups the directory. /// /// The path. /// If set to true the directory will be cleared if not empty. public static void SetupDirectory(string path, bool forceEmpty) { if (Directory.Exists(path)) { if (forceEmpty) { Utilities.DirectoryDelete(path); Directory.CreateDirectory(path); } } else { Directory.CreateDirectory(path); } } /// /// Clones the directory. /// /// The source folder path. /// The destination folder path. public static void CloneDirectory(string src, string dst) { if (Directory.Exists(dst)) Utilities.DirectoryDelete(dst); Utilities.DirectoryCopy(src, dst); } /// /// Clones the git repository from the remote url (full repository). /// /// The local path for close. /// The remote url. /// The commit to checkout. /// The custom arguments to add to the clone command. /// True if initialize submodules of the repository (recursive). public static void CloneGitRepo(string path, string url, string commit = null, string args = null, bool submodules = false) { if (!Directory.Exists(Path.Combine(path, Path.GetFileNameWithoutExtension(url), ".git"))) { string cmdLine = string.Format("clone \"{0}\" \"{1}\"", url, path); if (args != null) cmdLine += " " + args; if (submodules) cmdLine += " --recurse-submodules"; Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); if (submodules) Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); } if (commit != null) { Utilities.Run("git", string.Format("reset --hard {0}", commit), null, null, Utilities.RunOptions.None); } } /// /// Clones the git repository from the remote url. /// /// The local path for close. /// The remote url. /// The custom arguments to add to the clone command. /// True if initialize submodules of the repository (recursive). public static void CloneGitRepoFast(string path, string url, string args = null, bool submodules = false) { if (!Directory.Exists(Path.Combine(path, Path.GetFileNameWithoutExtension(url), ".git"))) { string cmdLine = string.Format("clone \"{0}\" \"{1}\" --depth 1", url, path); if (args != null) cmdLine += " " + args; if (submodules) cmdLine += " --recurse-submodules"; Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); if (submodules) Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); } } /// /// Clones the git repository from the remote url (clones a single branch). /// /// The local path for close. /// The remote url. /// The name of the branch to checkout. /// The commit to checkout. /// The custom arguments to add to the clone command. /// True if initialize submodules of the repository (recursive). public static void CloneGitRepoSingleBranch(string path, string url, string branch, string commit = null, string args = null, bool submodules = false) { if (!Directory.Exists(Path.Combine(path, ".git"))) { string cmdLine = string.Format("clone --single-branch --branch {2} \"{0}\" \"{1}\"", url, path, branch); if (commit == null) cmdLine += " --depth 1"; if (args != null) cmdLine += " " + args; if (submodules) cmdLine += " --recurse-submodules"; Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); if (submodules) Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); } if (commit != null) { Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.None); } } /// /// Changes the current branch to the given branch in the git repository (resets all the local changes). Does not perform pull command to update branch with remote repo. /// /// The local path that contains git repository. /// The name of the branch to checkout. /// The commit to checkout. /// The custom arguments to add to the clone command. /// True if initialize submodules of the repository (recursive). public static void GitCheckout(string path, string branch, string commit = null, string args = null, bool submodules = false) { string cmdLine = string.Format("checkout -B {0} origin/{0}", branch); if (args != null) cmdLine += " " + args; if (submodules) cmdLine += " --recurse-submodules"; Utilities.Run("git", cmdLine, null, path, Utilities.RunOptions.None); if (submodules) Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); if (commit != null) { Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.None); } } /// /// Resets all the local changes. /// /// The local path that contains git repository. public static void GitResetLocalChanges(string path) { Utilities.Run("git", "reset --hard", null, path, Utilities.RunOptions.None); } /// /// Runs the cmake tool. /// /// The path. /// The output platform. /// The output architecture. /// The custom arguments for the CMake. /// Custom environment variables to pass to the child process. public static void RunCmake(string path, TargetPlatform platform, TargetArchitecture architecture, string customArgs = null, Dictionary envVars = null) { string cmdLine; switch (platform) { case TargetPlatform.Windows: case TargetPlatform.XboxOne: case TargetPlatform.XboxScarlett: case TargetPlatform.UWP: { string arch; switch (architecture) { case TargetArchitecture.x86: arch = string.Empty; break; case TargetArchitecture.x64: arch = " Win64"; break; case TargetArchitecture.ARM: arch = " ARM"; break; case TargetArchitecture.ARM64: arch = " ARM64"; break; default: throw new InvalidArchitectureException(architecture); } cmdLine = string.Format("CMakeLists.txt -G \"Visual Studio 14 2015{0}\"", arch); break; } case TargetPlatform.Linux: case TargetPlatform.PS4: case TargetPlatform.PS5: { cmdLine = "CMakeLists.txt"; break; } case TargetPlatform.Switch: { cmdLine = string.Format("-DCMAKE_TOOLCHAIN_FILE=\"{1}\\Source\\Platforms\\Switch\\Binaries\\Data\\Switch.cmake\" -G \"NMake Makefiles\" -DCMAKE_MAKE_PROGRAM=\"{0}..\\..\\VC\\bin\\nmake.exe\"", Environment.GetEnvironmentVariable("VS140COMNTOOLS"), Globals.EngineRoot); break; } case TargetPlatform.Android: { var ndk = AndroidNdk.Instance.RootPath; var abi = AndroidToolchain.GetAbiName(architecture); var hostName = AndroidSdk.GetHostName(); cmdLine = string.Format("-DCMAKE_TOOLCHAIN_FILE=\"{0}/build/cmake/android.toolchain.cmake\" -DANDROID_NDK=\"{0}\" -DANDROID_STL=c++_shared -DANDROID_ABI={1} -DANDROID_PLATFORM=android-{2} -G \"MinGW Makefiles\" -DCMAKE_MAKE_PROGRAM=\"{0}/prebuilt/{3}/bin/make.exe\"", ndk, abi, Configuration.AndroidPlatformApi, hostName); break; } case TargetPlatform.Mac: { var arch = GetAppleArchName(architecture); cmdLine = string.Format("CMakeLists.txt -DCMAKE_OSX_DEPLOYMENT_TARGET=\"{0}\" -DCMAKE_OSX_ARCHITECTURES={1}", Configuration.MacOSXMinVer, arch); break; } case TargetPlatform.iOS: { var arch = GetAppleArchName(architecture); cmdLine = string.Format("CMakeLists.txt -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=\"{0}\" -DCMAKE_OSX_ARCHITECTURES={1}", Configuration.iOSMinVer, arch); break; } default: throw new InvalidPlatformException(platform); } if (customArgs != null) cmdLine += " " + customArgs; Utilities.Run("cmake", cmdLine, null, path, Utilities.RunOptions.None, envVars); } /// /// Gets the Apple architecture name (for toolchain). /// public static string GetAppleArchName(TargetArchitecture architecture) { string arch; switch (architecture) { case TargetArchitecture.x86: arch = "i386"; break; case TargetArchitecture.x64: arch = "x86_64"; break; case TargetArchitecture.ARM: arch = "arm"; break; case TargetArchitecture.ARM64: arch = "arm64"; break; default: throw new InvalidArchitectureException(architecture); } return arch; } /// /// Runs the bash script via Cygwin tool (native bash on platforms other than Windows). /// /// The path. /// The workspace folder. public static void RunCygwin(string path, string workspace = null) { RunBash(path, string.Empty, workspace); } /// /// Runs the bash script (executes natively on Unix platforms, uses Cygwin on Windows). /// /// The script or command path. /// The arguments. /// The workspace folder. /// Custom environment variables to pass to the child process. public static void RunBash(string path, string args = null, string workspace = null, Dictionary envVars = null) { switch (BuildPlatform) { case TargetPlatform.Windows: { // Find Cygwin var cygwinFolder = Environment.GetEnvironmentVariable("CYGWIN"); if (string.IsNullOrEmpty(cygwinFolder) || !Directory.Exists(cygwinFolder)) { cygwinFolder = "C:\\cygwin"; if (!Directory.Exists(cygwinFolder)) { cygwinFolder = "C:\\cygwin64"; if (!Directory.Exists(cygwinFolder)) throw new Exception("Missing Cygwin. Install Cygwin64 to C:\\cygwin or set CYGWIN env variable to install location folder."); } } var cygwinBinFolder = Path.Combine(cygwinFolder, "bin"); // Ensure that Cygwin binaries folder is in a PATH string envPath = null; envVars?.TryGetValue("PATH", out envPath); if (envPath == null || envPath.IndexOf(cygwinBinFolder, StringComparison.OrdinalIgnoreCase) == -1) { if (envVars == null) envVars = new Dictionary(); envVars["PATH"] = cygwinBinFolder; if (envPath != null) envVars["PATH"] += ";" + envPath; } // Get the executable file path if (path.EndsWith(".sh", StringComparison.OrdinalIgnoreCase)) { // Bash script if (args == null) args = path.Replace('\\', '/'); else args = path.Replace('\\', '/') + " " + args; path = Path.Combine(cygwinBinFolder, "bash.exe"); } else if (File.Exists(Path.Combine(cygwinBinFolder, path + ".exe"))) { // Tool (eg. make) path = Path.Combine(cygwinBinFolder, path + ".exe"); } else { throw new Exception("Cannot execute command " + path + " with args " + args); } break; } case TargetPlatform.Linux: case TargetPlatform.Mac: break; default: throw new InvalidPlatformException(BuildPlatform); } Utilities.Run(path, args, null, workspace, Utilities.RunOptions.ThrowExceptionOnError, envVars); } internal bool GetMsBuildForPlatform(TargetPlatform targetPlatform, out VisualStudioVersion vsVersion, out string msBuildPath) { // Some consoles don't support the latest Visual Studio 2022 vsVersion = VisualStudioVersion.VisualStudio2022; switch (targetPlatform) { case TargetPlatform.PS4: vsVersion = VisualStudioVersion.VisualStudio2017; break; case TargetPlatform.PS5: case TargetPlatform.Switch: vsVersion = VisualStudioVersion.VisualStudio2019; break; } if (vsVersion != VisualStudioVersion.VisualStudio2022) { var visualStudioInstances = VisualStudioInstance.GetInstances(); foreach (var visualStudioInstance in visualStudioInstances) { if (visualStudioInstance.Version <= vsVersion) { var toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\Current\\Bin\\MSBuild.exe"); if (!File.Exists(toolPath)) toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\15.0\\Bin\\MSBuild.exe"); if (File.Exists(toolPath)) { vsVersion = visualStudioInstance.Version; msBuildPath = toolPath; return true; } } } } msBuildPath = VCEnvironment.MSBuildPath; return false; } } }