summaryrefslogtreecommitdiffstats
path: root/testing
diff options
context:
space:
mode:
authorJoseph Brill <48932340+jcbrill@users.noreply.github.com>2024-10-11 14:22:43 (GMT)
committerJoseph Brill <48932340+jcbrill@users.noreply.github.com>2024-10-11 14:22:43 (GMT)
commit76839d932e31aeb092f31fd1d81922aba193b446 (patch)
tree251437187776e7a3bf9a495934ad6b3f348b9178 /testing
parent357ce53b076feba224507053641d3de175a82429 (diff)
downloadSCons-76839d932e31aeb092f31fd1d81922aba193b446.zip
SCons-76839d932e31aeb092f31fd1d81922aba193b446.tar.gz
SCons-76839d932e31aeb092f31fd1d81922aba193b446.tar.bz2
Update Tool/msvs.py, update MSVS tests; and add additional tests for MSVS multi-project and solution builds.
Tool/msvs.py: * Add "projectguid" argument to MSVSProject to enable user-defined GUID assignment per-project for multiple project solutions. * Project GUID priority: (1) MSVSProject "projectguid" argument, (2) SCons env MSVS_PROJECT_GUID value, (3) internally generated GUID. SCons env MSVS_PROJECT_GUID value should not be used for multiple project solutions without using projectguid arguments (otherwise, multiple projects will use the same GUID). * Store the project GUID as a node Tag when generating the MSVSProject and retrieve the project GUIDs using GetTag when generating the solution. * Move project node processing to a function so it can be called from multiple code locations. * Filter out solution nodes from project list (bugfix: auto_build_solution enabled and returned value used as project list includes auto-generated solution as a Project in the solution file). * Check for variant directory build of MSVSSolution and adjust src node accordingly similar to MSVSProject code. Bug fix: the solution file is generated in the build directory instead of the source directory. The placeholder solution file is now generated in the build directory and the solution file is generated in the source folder similar to the handling of project files. * Add project dsp nodes to the dsw source node list. This appears to always cause the project files to be generated before the solution files which is necessary to retrieve the project GUIDs for use in the solution file. This does change the behavior of clean for a project generated with auto_build_solution disabled and explicit solution generation: when the solution file is cleaned, the project files are also cleaned. The tests for vs 6.0-7.1 were changed accordingly. testing/framework/TestSConsMSVS.py: * Add known project GUID for single project tests and two known project GUIDs for dual project tests. * Add projectguid to MSVSProject arguments for single project test SConstruct/SConscript files. * Add PROJECT_BASENAME replaceable field to expected generated project file templates. * Add dual project and single solution templates. Tests: * Use known project guid for most tests which prevents have to hard-code generated GUIDs. * Add 4 (2x2) tests using two projects for combinations of auto_build_solutions settings (2) and variant directories (2). * Add 2 tests using two projects which use default GUID generation with and without variant directories.
Diffstat (limited to 'testing')
-rw-r--r--testing/framework/TestSConsMSVS.py269
1 files changed, 250 insertions, 19 deletions
diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py
index 1a075e8..910dfd2 100644
--- a/testing/framework/TestSConsMSVS.py
+++ b/testing/framework/TestSConsMSVS.py
@@ -50,7 +50,9 @@ from TestSCons import *
from TestSCons import __all__
-MSVS_PROJECT_GUID = "{00000000-0000-0000-0000-000000000000}"
+PROJECT_GUID = "{00000000-0000-0000-0000-000000000000}"
+PROJECT_GUID_1 = "{11111111-1111-1111-1111-111111111111}"
+PROJECT_GUID_2 = "{22222222-2222-2222-2222-222222222222}"
expected_dspfile_6_0 = '''\
# Microsoft Developer Studio Project File - Name="Test" - Package Owner=<4>
@@ -314,7 +316,6 @@ expected_vcprojfile_7_0 = """\
SConscript_contents_7_0 = """\
env=Environment(platform='win32', tools=['msvs'],
MSVS_VERSION='7.0',
- MSVS_PROJECT_GUID='%(MSVS_PROJECT_GUID)s',
HOST_ARCH='%(HOST_ARCH)s')
testsrc = ['test1.cpp', 'test2.cpp']
@@ -324,6 +325,7 @@ testresources = ['test.rc']
testmisc = ['readme.txt']
env.MSVSProject(target = 'Test.vcproj',
+ projectguid = '%(PROJECT_GUID)s',
slnguid = '{SLNGUID}',
srcs = testsrc,
incs = testincs,
@@ -442,7 +444,6 @@ expected_vcprojfile_7_1 = """\
SConscript_contents_7_1 = """\
env=Environment(platform='win32', tools=['msvs'],
MSVS_VERSION='7.1',
- MSVS_PROJECT_GUID='%(MSVS_PROJECT_GUID)s',
HOST_ARCH='%(HOST_ARCH)s')
testsrc = ['test1.cpp', 'test2.cpp']
@@ -452,6 +453,7 @@ testresources = ['test.rc']
testmisc = ['readme.txt']
env.MSVSProject(target = 'Test.vcproj',
+ projectguid = '%(PROJECT_GUID)s',
slnguid = '{SLNGUID}',
srcs = testsrc,
incs = testincs,
@@ -488,9 +490,9 @@ expected_vcprojfile_fmt = """\
<VisualStudioProject
\tProjectType="Visual C++"
\tVersion="%(TOOLS_VERSION)s"
-\tName="Test"
+\tName="%(PROJECT_BASENAME)s"
\tProjectGUID="<PROJECT_GUID>"
-\tRootNamespace="Test"
+\tRootNamespace="%(PROJECT_BASENAME)s"
<SCC_VCPROJ_INFO>
\tKeyword="MakeFileProj">
\t<Platforms>
@@ -508,10 +510,10 @@ expected_vcprojfile_fmt = """\
\t\t\t>
\t\t\t<Tool
\t\t\t\tName="VCNMakeTool"
-\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;Test.exe&quot;"
-\t\t\t\tReBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;Test.exe&quot;"
-\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct -c &quot;Test.exe&quot;"
-\t\t\t\tOutput="Test.exe"
+\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;%(PROJECT_BASENAME)s.exe&quot;"
+\t\t\t\tReBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;%(PROJECT_BASENAME)s.exe&quot;"
+\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct -c &quot;%(PROJECT_BASENAME)s.exe&quot;"
+\t\t\t\tOutput="%(PROJECT_BASENAME)s.exe"
\t\t\t\tPreprocessorDefinitions="DEF1;DEF2;DEF3=1234"
\t\t\t\tIncludeSearchPath="%(INCLUDE_DIRS)s"
\t\t\t\tForcedIncludes=""
@@ -581,9 +583,9 @@ expected_vcxprojfile_fmt = """\
\t\t</ProjectConfiguration>
\t</ItemGroup>
\t<PropertyGroup Label="Globals">
-\t\t<ProjectGuid>%(MSVS_PROJECT_GUID)s</ProjectGuid>
+\t\t<ProjectGuid>%(PROJECT_GUID)s</ProjectGuid>
<SCC_VCPROJ_INFO>
-\t\t<RootNamespace>Test</RootNamespace>
+\t\t<RootNamespace>%(PROJECT_BASENAME)s</RootNamespace>
\t\t<Keyword>MakeFileProj</Keyword>
\t\t<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
\t</PropertyGroup>
@@ -602,10 +604,10 @@ expected_vcxprojfile_fmt = """\
\t<PropertyGroup Label="UserMacros" />
\t<PropertyGroup>
\t<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
-\t\t<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;Test.exe&quot;</NMakeBuildCommandLine>
-\t\t<NMakeReBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;Test.exe&quot;</NMakeReBuildCommandLine>
-\t\t<NMakeCleanCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct -c &quot;Test.exe&quot;</NMakeCleanCommandLine>
-\t\t<NMakeOutput Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Test.exe</NMakeOutput>
+\t\t<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;%(PROJECT_BASENAME)s.exe&quot;</NMakeBuildCommandLine>
+\t\t<NMakeReBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct &quot;%(PROJECT_BASENAME)s.exe&quot;</NMakeReBuildCommandLine>
+\t\t<NMakeCleanCommandLine Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C &quot;<WORKPATH>&quot; -f SConstruct -c &quot;%(PROJECT_BASENAME)s.exe&quot;</NMakeCleanCommandLine>
+\t\t<NMakeOutput Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PROJECT_BASENAME)s.exe</NMakeOutput>
\t\t<NMakePreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">DEF1;DEF2;DEF3=1234</NMakePreprocessorDefinitions>
\t\t<NMakeIncludeSearchPath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(INCLUDE_DIRS)s</NMakeIncludeSearchPath>
\t\t<NMakeForcedIncludes Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(NMakeForcedIncludes)</NMakeForcedIncludes>
@@ -640,7 +642,6 @@ expected_vcxprojfile_fmt = """\
SConscript_contents_fmt = """\
env=Environment(platform='win32', tools=['msvs'], MSVS_VERSION='%(MSVS_VERSION)s',
- MSVS_PROJECT_GUID='%(MSVS_PROJECT_GUID)s',
CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')],
CPPPATH=['inc1', 'inc2'],
HOST_ARCH='%(HOST_ARCH)s')
@@ -652,6 +653,7 @@ testresources = ['test.rc']
testmisc = ['readme.txt']
env.MSVSProject(target = '%(PROJECT_FILE)s',
+ projectguid = '%(PROJECT_GUID)s',
slnguid = '{SLNGUID}',
srcs = testsrc,
incs = testincs,
@@ -662,6 +664,129 @@ env.MSVSProject(target = '%(PROJECT_FILE)s',
variant = 'Release')
"""
+expected_projects_slnfile_fmt = """\
+Microsoft Visual Studio Solution File, Format Version %(FORMAT_VERSION)s
+# Visual Studio %(VS_NUMBER)s
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%(PROJECT_NAME_1)s", "%(PROJECT_FILE_1)s", "<PROJECT_GUID_1>"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%(PROJECT_NAME_2)s", "%(PROJECT_FILE_2)s", "<PROJECT_GUID_2>"
+EndProject
+Global
+<SCC_SLN_INFO>
+\tGlobalSection(SolutionConfigurationPlatforms) = preSolution
+\t\tRelease|Win32 = Release|Win32
+\tEndGlobalSection
+\tGlobalSection(ProjectConfigurationPlatforms) = postSolution
+\t\t<PROJECT_GUID_1>.Release|Win32.ActiveCfg = Release|Win32
+\t\t<PROJECT_GUID_1>.Release|Win32.Build.0 = Release|Win32
+\t\t<PROJECT_GUID_2>.Release|Win32.ActiveCfg = Release|Win32
+\t\t<PROJECT_GUID_2>.Release|Win32.Build.0 = Release|Win32
+\tEndGlobalSection
+\tGlobalSection(SolutionProperties) = preSolution
+\t\tHideSolutionNode = FALSE
+\tEndGlobalSection
+EndGlobal
+"""
+
+SConscript_projects_contents_fmt = """\
+env=Environment(
+ platform='win32',
+ tools=['msvs'],
+ MSVS_VERSION='%(MSVS_VERSION)s',
+ CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')],
+ CPPPATH=['inc1', 'inc2'],
+ HOST_ARCH='%(HOST_ARCH)s',
+)
+
+testsrc = ['test1.cpp', 'test2.cpp']
+testincs = [r'sdk_dir\\sdk.h']
+testlocalincs = ['test.h']
+testresources = ['test.rc']
+testmisc = ['readme.txt']
+
+p1 = env.MSVSProject(
+ target = '%(PROJECT_FILE_1)s',
+ projectguid = '%(PROJECT_GUID_1)s',
+ slnguid = '{SLNGUID}',
+ srcs = testsrc,
+ incs = testincs,
+ localincs = testlocalincs,
+ resources = testresources,
+ misc = testmisc,
+ buildtarget = 'Test_1.exe',
+ variant = 'Release',
+ auto_build_solution = %(AUTOBUILD_SOLUTION)s,
+)
+
+p2 = env.MSVSProject(
+ target = '%(PROJECT_FILE_2)s',
+ projectguid = '%(PROJECT_GUID_2)s',
+ slnguid = '{SLNGUID}',
+ srcs = testsrc,
+ incs = testincs,
+ localincs = testlocalincs,
+ resources = testresources,
+ misc = testmisc,
+ buildtarget = 'Test_2.exe',
+ variant = 'Release',
+ auto_build_solution = %(AUTOBUILD_SOLUTION)s,
+)
+
+env.MSVSSolution(
+ target = '%(SOLUTION_FILE)s',
+ projects = [p1, p2],
+ variant = 'Release',
+)
+"""
+
+SConscript_projects_defaultguids_contents_fmt = """\
+env=Environment(
+ platform='win32',
+ tools=['msvs'],
+ MSVS_VERSION='%(MSVS_VERSION)s',
+ CPPDEFINES=['DEF1', 'DEF2',('DEF3','1234')],
+ CPPPATH=['inc1', 'inc2'],
+ HOST_ARCH='%(HOST_ARCH)s',
+)
+
+testsrc = ['test1.cpp', 'test2.cpp']
+testincs = [r'sdk_dir\\sdk.h']
+testlocalincs = ['test.h']
+testresources = ['test.rc']
+testmisc = ['readme.txt']
+
+p1 = env.MSVSProject(
+ target = '%(PROJECT_FILE_1)s',
+ slnguid = '{SLNGUID}',
+ srcs = testsrc,
+ incs = testincs,
+ localincs = testlocalincs,
+ resources = testresources,
+ misc = testmisc,
+ buildtarget = 'Test_1.exe',
+ variant = 'Release',
+ auto_build_solution = %(AUTOBUILD_SOLUTION)s,
+)
+
+p2 = env.MSVSProject(
+ target = '%(PROJECT_FILE_2)s',
+ slnguid = '{SLNGUID}',
+ srcs = testsrc,
+ incs = testincs,
+ localincs = testlocalincs,
+ resources = testresources,
+ misc = testmisc,
+ buildtarget = 'Test_2.exe',
+ variant = 'Release',
+ auto_build_solution = %(AUTOBUILD_SOLUTION)s,
+)
+
+env.MSVSSolution(
+ target = '%(SOLUTION_FILE)s',
+ projects = [p1, p2],
+ variant = 'Release',
+)
+"""
def get_tested_proj_file_vc_versions():
"""
@@ -728,7 +853,7 @@ print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions(env=Non
python = sys.executable
if project_guid is None:
- project_guid = MSVS_PROJECT_GUID
+ project_guid = PROJECT_GUID
if 'SCONS_LIB_DIR' in os.environ:
exec_script_main = f"from os.path import join; import sys; sys.path = [ r'{os.environ['SCONS_LIB_DIR']}' ] + sys.path; import SCons.Script; SCons.Script.main()"
@@ -944,8 +1069,11 @@ print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions(env=Non
fmt = expected_vcxprojfile_fmt
else:
fmt = expected_vcprojfile_fmt
+ project_filename = os.path.split(project_file)[-1]
+ project_basename = os.path.splitext(project_filename)[0]
return fmt % {
- 'MSVS_PROJECT_GUID': MSVS_PROJECT_GUID,
+ 'PROJECT_BASENAME': project_basename,
+ 'PROJECT_GUID': PROJECT_GUID,
'TOOLS_VERSION': self._get_vcxproj_file_tools_version(vc_version),
'INCLUDE_DIRS': self._get_vcxproj_file_cpp_path(dirs),
'PLATFORM_TOOLSET': self._get_vcxproj_file_platform_toolset(vc_version),
@@ -955,10 +1083,113 @@ print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions(env=Non
return SConscript_contents_fmt % {
'HOST_ARCH': self.get_vs_host_arch(),
'MSVS_VERSION': vc_version,
- 'MSVS_PROJECT_GUID': MSVS_PROJECT_GUID,
+ 'PROJECT_GUID': PROJECT_GUID,
'PROJECT_FILE': project_file,
}
+ def msvs_substitute_projects(
+ self, input, *,
+ subdir=None,
+ sconscript=None,
+ python=None,
+ project_guid_1=None,
+ project_guid_2=None,
+ vcproj_sccinfo: str='',
+ sln_sccinfo: str=''
+ ):
+ if not hasattr(self, '_msvs_versions'):
+ self.msvs_versions()
+
+ if subdir:
+ workpath = self.workpath(subdir)
+ else:
+ workpath = self.workpath()
+
+ if sconscript is None:
+ sconscript = self.workpath('SConstruct')
+
+ if python is None:
+ python = sys.executable
+
+ if project_guid_1 is None:
+ project_guid_1 = PROJECT_GUID_1
+
+ if project_guid_2 is None:
+ project_guid_2 = PROJECT_GUID_2
+
+ if 'SCONS_LIB_DIR' in os.environ:
+ exec_script_main = f"from os.path import join; import sys; sys.path = [ r'{os.environ['SCONS_LIB_DIR']}' ] + sys.path; import SCons.Script; SCons.Script.main()"
+ else:
+ exec_script_main = f"from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-{self.scons_version}'), join(sys.prefix, 'scons-{self.scons_version}'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()"
+ exec_script_main_xml = exec_script_main.replace("'", "&apos;")
+
+ result = input.replace(r'<WORKPATH>', workpath)
+ result = result.replace(r'<PYTHON>', python)
+ result = result.replace(r'<SCONSCRIPT>', sconscript)
+ result = result.replace(r'<SCONS_SCRIPT_MAIN>', exec_script_main)
+ result = result.replace(r'<SCONS_SCRIPT_MAIN_XML>', exec_script_main_xml)
+ result = result.replace(r'<PROJECT_GUID_1>', project_guid_1)
+ result = result.replace(r'<PROJECT_GUID_2>', project_guid_2)
+ result = result.replace('<SCC_VCPROJ_INFO>\n', vcproj_sccinfo)
+ result = result.replace('<SCC_SLN_INFO>\n', sln_sccinfo)
+ return result
+
+ def get_expected_projects_proj_file_contents(self, vc_version, dirs, project_file, project_guid):
+ """Returns the expected .vcxproj file contents"""
+ if project_file.endswith('.vcxproj'):
+ fmt = expected_vcxprojfile_fmt
+ else:
+ fmt = expected_vcprojfile_fmt
+ project_filename = os.path.split(project_file)[-1]
+ project_basename = os.path.splitext(project_filename)[0]
+ return fmt % {
+ 'PROJECT_BASENAME': project_basename,
+ 'PROJECT_GUID': project_guid,
+ 'TOOLS_VERSION': self._get_vcxproj_file_tools_version(vc_version),
+ 'INCLUDE_DIRS': self._get_vcxproj_file_cpp_path(dirs),
+ 'PLATFORM_TOOLSET': self._get_vcxproj_file_platform_toolset(vc_version),
+ }
+
+ def get_expected_projects_sln_file_contents(
+ self, vc_version,
+ project_file_1, project_file_2,
+ ):
+ return expected_projects_slnfile_fmt % {
+ 'FORMAT_VERSION': self._get_solution_file_format_version(vc_version),
+ 'VS_NUMBER': self._get_solution_file_vs_number(vc_version),
+ 'PROJECT_NAME_1': project_file_1.split('.')[0],
+ 'PROJECT_FILE_1': project_file_1,
+ 'PROJECT_NAME_2': project_file_2.split('.')[0],
+ 'PROJECT_FILE_2': project_file_2,
+ }
+
+ def get_expected_projects_sconscript_file_contents(
+ self, vc_version,
+ project_file_1, project_file_2, solution_file,
+ autobuild_solution=0,
+ default_guids=False,
+ ):
+
+ values = {
+ 'HOST_ARCH': self.get_vs_host_arch(),
+ 'MSVS_VERSION': vc_version,
+ 'PROJECT_FILE_1': project_file_1,
+ 'PROJECT_FILE_2': project_file_2,
+ 'SOLUTION_FILE': solution_file,
+ "AUTOBUILD_SOLUTION": autobuild_solution,
+ }
+
+ if default_guids:
+ format = SConscript_projects_defaultguids_contents_fmt
+ else:
+ format = SConscript_projects_contents_fmt
+
+ values.update({
+ 'PROJECT_GUID_1': PROJECT_GUID_1,
+ 'PROJECT_GUID_2': PROJECT_GUID_2,
+ })
+ return format % values
+
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil