Fix .sln project generation logic for Rider to support all OS and all C++ toolchains
Co-authored-by: Andreia Gaita <shana@spoiledcat.net> Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
This commit is contained in:
parent
e585e6a3eb
commit
6ff86e49cf
@ -15,3 +15,7 @@ indent_style = space
|
|||||||
[{*.{yml,yaml},.clang{-format,-tidy,d}}]
|
[{*.{yml,yaml},.clang{-format,-tidy,d}}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[{*.props,*.vcxproj}]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
102
methods.py
102
methods.py
@ -14,6 +14,7 @@ from pathlib import Path
|
|||||||
from typing import Generator, List, Optional, Union, cast
|
from typing import Generator, List, Optional, Union, cast
|
||||||
|
|
||||||
from misc.utility.color import print_error, print_info, print_warning
|
from misc.utility.color import print_error, print_info, print_warning
|
||||||
|
from platform_methods import detect_arch
|
||||||
|
|
||||||
# Get the "Godot" folder name ahead of time
|
# Get the "Godot" folder name ahead of time
|
||||||
base_folder = Path(__file__).resolve().parent
|
base_folder = Path(__file__).resolve().parent
|
||||||
@ -1067,8 +1068,22 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
platform = env["platform"]
|
platform = env["platform"]
|
||||||
target = env["target"]
|
target = env["target"]
|
||||||
arch = env["arch"]
|
arch = env["arch"]
|
||||||
|
host_arch = detect_arch()
|
||||||
|
|
||||||
|
host_platform = "windows"
|
||||||
|
if (
|
||||||
|
sys.platform.startswith("linux")
|
||||||
|
or sys.platform.startswith("dragonfly")
|
||||||
|
or sys.platform.startswith("freebsd")
|
||||||
|
or sys.platform.startswith("netbsd")
|
||||||
|
or sys.platform.startswith("openbsd")
|
||||||
|
):
|
||||||
|
host_platform = "linuxbsd"
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
host_platform = "macos"
|
||||||
|
|
||||||
vs_configuration = {}
|
vs_configuration = {}
|
||||||
|
host_vs_configuration = {}
|
||||||
common_build_prefix = []
|
common_build_prefix = []
|
||||||
confs = []
|
confs = []
|
||||||
for x in sorted(glob.glob("platform/*")):
|
for x in sorted(glob.glob("platform/*")):
|
||||||
@ -1097,6 +1112,12 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
if platform == platform_name:
|
if platform == platform_name:
|
||||||
common_build_prefix = msvs.get_build_prefix(env)
|
common_build_prefix = msvs.get_build_prefix(env)
|
||||||
vs_configuration = vsconf
|
vs_configuration = vsconf
|
||||||
|
if platform_name == host_platform:
|
||||||
|
host_vs_configuration = vsconf
|
||||||
|
for a in vsconf["arches"]:
|
||||||
|
if host_arch == a["architecture"]:
|
||||||
|
host_arch = a["platform"]
|
||||||
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -1254,12 +1275,11 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
properties.append(
|
properties.append(
|
||||||
"<ActiveProjectItemList_%s>;%s;</ActiveProjectItemList_%s>" % (x, ";".join(itemlist[x]), x)
|
"<ActiveProjectItemList_%s>;%s;</ActiveProjectItemList_%s>" % (x, ";".join(itemlist[x]), x)
|
||||||
)
|
)
|
||||||
output = f"bin\\godot{env['PROGSUFFIX']}"
|
output = os.path.join("bin", f"godot{env['PROGSUFFIX']}")
|
||||||
|
|
||||||
with open("misc/msvs/props.template", "r", encoding="utf-8") as file:
|
with open("misc/msvs/props.template", "r", encoding="utf-8") as file:
|
||||||
props_template = file.read()
|
props_template = file.read()
|
||||||
|
|
||||||
props_template = props_template.replace("%%VSCONF%%", vsconf)
|
|
||||||
props_template = props_template.replace("%%CONDITION%%", condition)
|
props_template = props_template.replace("%%CONDITION%%", condition)
|
||||||
props_template = props_template.replace("%%PROPERTIES%%", "\n ".join(properties))
|
props_template = props_template.replace("%%PROPERTIES%%", "\n ".join(properties))
|
||||||
props_template = props_template.replace("%%EXTRA_ITEMS%%", "\n ".join(extraItems))
|
props_template = props_template.replace("%%EXTRA_ITEMS%%", "\n ".join(extraItems))
|
||||||
@ -1272,6 +1292,7 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
|
|
||||||
proplist = [str(j) for j in env["CPPPATH"]]
|
proplist = [str(j) for j in env["CPPPATH"]]
|
||||||
proplist += [str(j) for j in env.get("VSHINT_INCLUDES", [])]
|
proplist += [str(j) for j in env.get("VSHINT_INCLUDES", [])]
|
||||||
|
proplist += [str(j) for j in get_default_include_paths(env)]
|
||||||
props_template = props_template.replace("%%INCLUDES%%", ";".join(proplist))
|
props_template = props_template.replace("%%INCLUDES%%", ";".join(proplist))
|
||||||
|
|
||||||
proplist = env["CCFLAGS"]
|
proplist = env["CCFLAGS"]
|
||||||
@ -1307,17 +1328,17 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
|
|
||||||
commands = "scons"
|
commands = "scons"
|
||||||
if len(common_build_prefix) == 0:
|
if len(common_build_prefix) == 0:
|
||||||
commands = "echo Starting SCons && cmd /V /C " + commands
|
commands = "echo Starting SCons & " + commands
|
||||||
else:
|
else:
|
||||||
common_build_prefix[0] = "echo Starting SCons && cmd /V /C " + common_build_prefix[0]
|
common_build_prefix[0] = "echo Starting SCons & " + common_build_prefix[0]
|
||||||
|
|
||||||
cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
|
cmd = " ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
|
||||||
props_template = props_template.replace("%%BUILD%%", cmd)
|
props_template = props_template.replace("%%BUILD%%", cmd)
|
||||||
|
|
||||||
cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)])
|
cmd = " ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)])
|
||||||
props_template = props_template.replace("%%REBUILD%%", cmd)
|
props_template = props_template.replace("%%REBUILD%%", cmd)
|
||||||
|
|
||||||
cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + cmd_clean)])
|
cmd = " ".join(common_build_prefix + [" ".join([commands] + cmd_clean)])
|
||||||
props_template = props_template.replace("%%CLEAN%%", cmd)
|
props_template = props_template.replace("%%CLEAN%%", cmd)
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
@ -1348,18 +1369,45 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
section2 = []
|
section2 = []
|
||||||
for conf in confs:
|
for conf in confs:
|
||||||
godot_platform = conf["platform"]
|
godot_platform = conf["platform"]
|
||||||
|
has_editor = "editor" in conf["targets"]
|
||||||
|
|
||||||
|
# Skip any platforms that can build the editor and don't match the host platform.
|
||||||
|
#
|
||||||
|
# When both Windows and Mac define an editor target, it's defined as platform+target+arch (windows+editor+x64 for example).
|
||||||
|
# VS only supports two attributes, a "Configuration" and a "Platform", and we currently map our target to the Configuration
|
||||||
|
# (i.e. editor/template_debug/template_release), and our architecture to the "Platform" (i.e. x64, arm64, etc).
|
||||||
|
# Those two are not enough to disambiguate multiple godot targets for different godot platforms with the same architecture,
|
||||||
|
# i.e. editor|x64 would currently match both windows editor intel 64 and linux editor intel 64.
|
||||||
|
#
|
||||||
|
# TODO: More work is needed in order to support generating VS projects that unambiguously support all platform+target+arch variations.
|
||||||
|
# The VS "Platform" has to be a known architecture that VS recognizes, so we can only play around with the "Configuration" part of the combo.
|
||||||
|
if has_editor and godot_platform != host_vs_configuration["platform"]:
|
||||||
|
continue
|
||||||
|
|
||||||
for p in conf["arches"]:
|
for p in conf["arches"]:
|
||||||
sln_plat = p["platform"]
|
sln_plat = p["platform"]
|
||||||
proj_plat = sln_plat
|
proj_plat = sln_plat
|
||||||
godot_arch = p["architecture"]
|
godot_arch = p["architecture"]
|
||||||
|
|
||||||
# Redirect editor configurations for non-Windows platforms to the Windows one, so the solution has all the permutations
|
# Redirect editor configurations for platforms that don't support the editor target to the default editor target on the
|
||||||
# and VS doesn't complain about missing project configurations.
|
# active host platform, so the solution has all the permutations and VS doesn't complain about missing project configurations.
|
||||||
# These configurations are disabled, so they show up but won't build.
|
# These configurations are disabled, so they show up but won't build.
|
||||||
if godot_platform != "windows":
|
if not has_editor:
|
||||||
section1 += [f"editor|{sln_plat} = editor|{proj_plat}"]
|
section1 += [f"editor|{sln_plat} = editor|{proj_plat}"]
|
||||||
section2 += [
|
section2 += [f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{host_arch}"]
|
||||||
f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{proj_plat}",
|
|
||||||
|
configurations += [
|
||||||
|
f'<ProjectConfiguration Include="editor|{proj_plat}">',
|
||||||
|
" <Configuration>editor</Configuration>",
|
||||||
|
f" <Platform>{proj_plat}</Platform>",
|
||||||
|
"</ProjectConfiguration>",
|
||||||
|
]
|
||||||
|
|
||||||
|
properties += [
|
||||||
|
f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">",
|
||||||
|
" <GodotConfiguration>editor</GodotConfiguration>",
|
||||||
|
f" <GodotPlatform>{proj_plat}</GodotPlatform>",
|
||||||
|
"</PropertyGroup>",
|
||||||
]
|
]
|
||||||
|
|
||||||
for t in conf["targets"]:
|
for t in conf["targets"]:
|
||||||
@ -1383,21 +1431,6 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||||||
"</PropertyGroup>",
|
"</PropertyGroup>",
|
||||||
]
|
]
|
||||||
|
|
||||||
if godot_platform != "windows":
|
|
||||||
configurations += [
|
|
||||||
f'<ProjectConfiguration Include="editor|{proj_plat}">',
|
|
||||||
" <Configuration>editor</Configuration>",
|
|
||||||
f" <Platform>{proj_plat}</Platform>",
|
|
||||||
"</ProjectConfiguration>",
|
|
||||||
]
|
|
||||||
|
|
||||||
properties += [
|
|
||||||
f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">",
|
|
||||||
" <GodotConfiguration>editor</GodotConfiguration>",
|
|
||||||
f" <GodotPlatform>{proj_plat}</GodotPlatform>",
|
|
||||||
"</PropertyGroup>",
|
|
||||||
]
|
|
||||||
|
|
||||||
p = f"{project_name}.{godot_platform}.{godot_target}.{godot_arch}.generated.props"
|
p = f"{project_name}.{godot_platform}.{godot_target}.{godot_arch}.generated.props"
|
||||||
imports += [
|
imports += [
|
||||||
f'<Import Project="$(MSBuildProjectDirectory)\\{p}" Condition="Exists(\'$(MSBuildProjectDirectory)\\{p}\')"/>'
|
f'<Import Project="$(MSBuildProjectDirectory)\\{p}" Condition="Exists(\'$(MSBuildProjectDirectory)\\{p}\')"/>'
|
||||||
@ -1606,3 +1639,18 @@ def to_raw_cstring(value: Union[str, List[str]]) -> str:
|
|||||||
else:
|
else:
|
||||||
# Wrap multiple segments in parenthesis to suppress `string-concatenation` warnings on clang.
|
# Wrap multiple segments in parenthesis to suppress `string-concatenation` warnings on clang.
|
||||||
return "({})".format(" ".join(f'R"<!>({segment.decode()})<!>"' for segment in split))
|
return "({})".format(" ".join(f'R"<!>({segment.decode()})<!>"' for segment in split))
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_include_paths(env):
|
||||||
|
if env.msvc:
|
||||||
|
return []
|
||||||
|
compiler = env.subst("$CXX")
|
||||||
|
target = os.path.join(env.Dir("#main").abspath, "main.cpp")
|
||||||
|
args = [compiler, target, "-x", "c++", "-v"]
|
||||||
|
ret = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||||
|
output = ret.stdout
|
||||||
|
match = re.search(r"#include <\.\.\.> search starts here:([\S\s]*)End of search list.", output)
|
||||||
|
if not match:
|
||||||
|
print_warning("Failed to find the include paths in the compiler output.")
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in match[1].strip().splitlines()]
|
||||||
|
19
misc/msvs/nmake.substitution.props
Normal file
19
misc/msvs/nmake.substitution.props
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- override the PlatformToolset, which is set in the godot.vcxproj-->
|
||||||
|
<!-- Unknown matches to a set of conservative rules for the code analysis-->
|
||||||
|
<PlatformToolset>Unknown</PlatformToolset>
|
||||||
|
<LocalDebuggerCommand Condition="'$(LocalDebuggerCommand)' == ''">$(NMakeOutput)</LocalDebuggerCommand>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- Build/Rebuild/Clean targets for NMake are defined in MSVC, so we need to provide them, when using MSBuild without MSVC targets -->
|
||||||
|
<Target Name="Build">
|
||||||
|
<Exec Command="$(NMakeBuildCommandLine)"/>
|
||||||
|
</Target>
|
||||||
|
<Target Name="Rebuild">
|
||||||
|
<Exec Command="$(NMakeReBuildCommandLine)"/>
|
||||||
|
</Target>
|
||||||
|
<Target Name="Clean">
|
||||||
|
<Exec Command="$(NMakeCleanCommandLine)"/>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup Condition="'$(GodotConfiguration)|$(GodotPlatform)'=='%%VSCONF%%'">
|
<PropertyGroup Condition="%%CONDITION%%">
|
||||||
<NMakeBuildCommandLine>%%BUILD%%</NMakeBuildCommandLine>
|
<NMakeBuildCommandLine>%%BUILD%%</NMakeBuildCommandLine>
|
||||||
<NMakeReBuildCommandLine>%%REBUILD%%</NMakeReBuildCommandLine>
|
<NMakeReBuildCommandLine>%%REBUILD%%</NMakeReBuildCommandLine>
|
||||||
<NMakeCleanCommandLine>%%CLEAN%%</NMakeCleanCommandLine>
|
<NMakeCleanCommandLine>%%CLEAN%%</NMakeCleanCommandLine>
|
||||||
@ -18,6 +18,9 @@
|
|||||||
<ItemGroup Condition="%%CONDITION%%">
|
<ItemGroup Condition="%%CONDITION%%">
|
||||||
%%EXTRA_ITEMS%%
|
%%EXTRA_ITEMS%%
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Build/Rebuild/Clean targets for NMake are defined in MSVC, so we need to provide them, when using MSBuild without MSVC targets -->
|
||||||
|
<Import Project="$(MSBuildProjectDirectory)\misc\msvs\nmake.substitution.props" Condition="%%CONDITION%% And !Exists('$(VCTargetsPath)\Microsoft.Cpp.targets')" />
|
||||||
</Project>
|
</Project>
|
||||||
<!-- CHECKSUM
|
<!-- CHECKSUM
|
||||||
%%HASH%%
|
%%HASH%%
|
||||||
|
@ -10,31 +10,32 @@
|
|||||||
<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
|
<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
%%PROPERTIES%%
|
%%PROPERTIES%%
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.Default.props') "/>
|
||||||
<PropertyGroup Label="Configuration">
|
<PropertyGroup Label="Configuration">
|
||||||
<ConfigurationType>Makefile</ConfigurationType>
|
<ConfigurationType>Makefile</ConfigurationType>
|
||||||
<UseOfMfc>false</UseOfMfc>
|
<UseOfMfc>false</UseOfMfc>
|
||||||
<PlatformToolset>v143</PlatformToolset>
|
<PlatformToolset>v143</PlatformToolset> <!--Might be overridden in the platform specific import or Microsoft.Cpp.$(GodotPlatform).user.props -->
|
||||||
|
<DefaultPlatformToolset Condition="'$(DefaultPlatformToolset)'==''"/> <!--Workaround until https://youtrack.jetbrains.com/issue/RIDER-123783 is resolved. -->
|
||||||
<OutDir>$(SolutionDir)\bin\$(GodotPlatform)\$(GodotConfiguration)\</OutDir>
|
<OutDir>$(SolutionDir)\bin\$(GodotPlatform)\$(GodotConfiguration)\</OutDir>
|
||||||
<IntDir>obj\$(GodotPlatform)\$(GodotConfiguration)\</IntDir>
|
<IntDir>obj\$(GodotPlatform)\$(GodotConfiguration)\</IntDir>
|
||||||
<LayoutDir>$(OutDir)\Layout</LayoutDir>
|
<LayoutDir>$(OutDir)\Layout</LayoutDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.props') "/>
|
||||||
<ImportGroup Label="ExtensionSettings">
|
<ImportGroup Label="ExtensionSettings">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
<ImportGroup Label="PropertySheets">
|
<ImportGroup Label="PropertySheets">
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props')" Label="LocalAppDataPlatform" />
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props" Condition="Exists('$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
<PropertyGroup Label="UserMacros" />
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
%%IMPORTS%%
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
|
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
|
||||||
<ActiveProjectItemList></ActiveProjectItemList>
|
<ActiveProjectItemList></ActiveProjectItemList>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
%%IMPORTS%%
|
|
||||||
<ItemGroup Condition="'$(IncludeListImported)'==''">
|
<ItemGroup Condition="'$(IncludeListImported)'==''">
|
||||||
%%DEFAULT_ITEMS%%
|
%%DEFAULT_ITEMS%%
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.targets') "/>
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
11
platform/linuxbsd/msvs.py
Normal file
11
platform/linuxbsd/msvs.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Tuples with the name of the arch
|
||||||
|
def get_platforms():
|
||||||
|
return [("x64", "x86_64")]
|
||||||
|
|
||||||
|
|
||||||
|
def get_configurations():
|
||||||
|
return ["editor", "template_debug", "template_release"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_prefix(env):
|
||||||
|
return []
|
11
platform/macos/msvs.py
Normal file
11
platform/macos/msvs.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Tuples with the name of the arch
|
||||||
|
def get_platforms():
|
||||||
|
return [("arm64", "arm64"), ("x64", "x86_64")]
|
||||||
|
|
||||||
|
|
||||||
|
def get_configurations():
|
||||||
|
return ["editor", "template_debug", "template_release"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_prefix(env):
|
||||||
|
return []
|
@ -12,9 +12,13 @@ def get_configurations():
|
|||||||
|
|
||||||
|
|
||||||
def get_build_prefix(env):
|
def get_build_prefix(env):
|
||||||
|
if not env.msvc:
|
||||||
|
return []
|
||||||
batch_file = methods.find_visual_c_batch_file(env)
|
batch_file = methods.find_visual_c_batch_file(env)
|
||||||
return [
|
return [
|
||||||
|
"cmd /V /C",
|
||||||
"set "plat=$(PlatformTarget)"",
|
"set "plat=$(PlatformTarget)"",
|
||||||
"(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))",
|
"^& (if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))",
|
||||||
f"call "{batch_file}" !plat!",
|
f"^& call "{batch_file}" !plat!",
|
||||||
|
"^&",
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user