Skip to content

Commit baa1e9f

Browse files
mjcarrollBi0T1Nazeey
authored
Backport the python3 embedSdf script variant (#1240)
Signed-off-by: Michael Carroll <[email protected]> Co-authored-by: Bi0T1N <[email protected]> Co-authored-by: Addisu Z. Taddese <[email protected]>
1 parent f1d13b4 commit baa1e9f

File tree

3 files changed

+281
-7
lines changed

3 files changed

+281
-7
lines changed

sdf/CMakeLists.txt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,31 @@ add_subdirectory(1.10)
1212
add_custom_target(schema)
1313
add_dependencies(schema schema1_10)
1414

15+
# Optionally use the python script (default in sdf14+)
16+
if (EMBEDSDF_PY)
17+
if (NOT Python3_Interpreter_FOUND)
18+
gz_build_error("Python is required to generate the C++ file with the SDF content")
19+
endif()
20+
1521
# Generate the EmbeddedSdf.cc file, which contains all the supported SDF
1622
# descriptions in a map of strings. The parser.cc file uses EmbeddedSdf.hh.
17-
execute_process(
18-
COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.rb
19-
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
20-
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
21-
)
23+
execute_process(
24+
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.py
25+
--output-file "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
26+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
27+
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
28+
)
29+
else()
30+
execute_process(
31+
COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.rb
32+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
33+
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
34+
)
35+
endif()
36+
2237

23-
# Generate aggregated SDF description files for use by the sdformat.org
24-
# website. If the description files change, the generated full*.sdf files need
38+
# Generate aggregated SDF description files for use by the sdformat.org
39+
# website. If the description files change, the generated full*.sdf files need
2540
# to be removed before running this target.
2641
if (GZ_PROGRAM)
2742

sdf/embedSdf.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script for generating a C++ file that contains the content from all SDF files
4+
"""
5+
6+
from typing import List, Optional
7+
8+
import argparse
9+
import inspect
10+
import sys
11+
from pathlib import Path
12+
13+
14+
# The list of supported SDF specification versions. This will let us drop
15+
# versions without removing the directories.
16+
SUPPORTED_SDF_VERSIONS = [
17+
"1.10",
18+
"1.9",
19+
"1.8",
20+
"1.7",
21+
"1.6",
22+
"1.5",
23+
"1.4",
24+
"1.3",
25+
"1.2",
26+
]
27+
28+
# The list of supported SDF conversions. This list includes versions that
29+
# a user can convert an existing SDF version to.
30+
SUPPORTED_SDF_CONVERSIONS = ["1.10", "1.9", "1.8", "1.7", "1.6", "1.5", "1.4", "1.3"]
31+
32+
# whitespace indentation for C++ code
33+
INDENTATION = " "
34+
35+
# newline character
36+
NEWLINE = "\n"
37+
38+
39+
def get_copyright_notice() -> str:
40+
"""
41+
Provides the copyright notice for the C++ file
42+
43+
:returns: copyright notice
44+
"""
45+
res = inspect.cleandoc(
46+
"""
47+
/*
48+
* Copyright 2022 Open Source Robotics Foundation
49+
*
50+
* Licensed under the Apache License, Version 2.0 (the "License");
51+
* you may not use this file except in compliance with the License.
52+
* You may obtain a copy of the License at
53+
*
54+
* http://www.apache.org/licenses/LICENSE-2.0
55+
*
56+
* Unless required by applicable law or agreed to in writing, software
57+
* distributed under the License is distributed on an "AS IS" BASIS,
58+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59+
* See the License for the specific language governing permissions and
60+
* limitations under the License.
61+
*
62+
*/
63+
"""
64+
)
65+
return res + 2 * NEWLINE
66+
67+
68+
def get_file_header_prolog() -> str:
69+
"""
70+
Provides the include statement, namespace and variable declaration of the C++ file
71+
72+
:returns: prolog of the C++ file
73+
"""
74+
res = inspect.cleandoc(
75+
"""
76+
#include "EmbeddedSdf.hh"
77+
78+
namespace sdf
79+
{
80+
inline namespace SDF_VERSION_NAMESPACE
81+
{
82+
/////////////////////////////////////////////////
83+
const std::map<std::string, std::string> &GetEmbeddedSdf()
84+
{
85+
static const std::map<std::string, std::string> result {
86+
"""
87+
)
88+
return res + NEWLINE
89+
90+
91+
def embed_sdf_content(arg_path: str, arg_file_content: str) -> str:
92+
"""
93+
Generates a string pair with the folder and filename
94+
as well as the content of the file
95+
96+
:param arg_path: Foldername and filename of the SDF
97+
:param arg_file_content: Content of the provided file
98+
:returns: raw string literal mapping pair for the std::map
99+
"""
100+
res = []
101+
res.append("// NOLINT")
102+
res.append("{")
103+
res.append(f'"{arg_path}",')
104+
res.append('R"__sdf_literal__(')
105+
res.append(f"{arg_file_content}")
106+
res.append(')__sdf_literal__"')
107+
res.append("}")
108+
return NEWLINE.join(res)
109+
110+
111+
def get_file_header_epilog() -> str:
112+
"""
113+
Provides the return statement and the closing brackets of the C++ file
114+
115+
:returns: epilog of the C++ file
116+
"""
117+
res = inspect.cleandoc(
118+
"""
119+
};
120+
121+
return result;
122+
}
123+
124+
}
125+
} // namespace sdf
126+
127+
"""
128+
)
129+
return NEWLINE + res
130+
131+
132+
def write_output(file_content: str, output_filename: str) -> None:
133+
"""
134+
Print the content of the EmbeddedSdf.cc to a file
135+
"""
136+
copyright_notice = get_copyright_notice()
137+
prolog = get_file_header_prolog()
138+
epilog = get_file_header_epilog()
139+
output_content = copyright_notice + prolog + file_content + epilog
140+
141+
with open(output_filename, "w", encoding="utf8") as output_file:
142+
output_file.write(output_content)
143+
144+
145+
def collect_file_locations() -> List[Path]:
146+
paths: List[Path] = []
147+
148+
for sdf_version in SUPPORTED_SDF_VERSIONS:
149+
paths.extend(Path(sdf_version).glob("*.sdf"))
150+
for sdf_conversion in SUPPORTED_SDF_CONVERSIONS:
151+
paths.extend(Path(sdf_conversion).glob("*.convert"))
152+
return paths
153+
154+
155+
def generate_map_content(paths: List[Path], relative_to: Optional[str] = None) -> str:
156+
'''
157+
Generate the EmbeddedSdf.cc content
158+
'''
159+
content = []
160+
for path in paths:
161+
with open(path, "r", encoding="utf8") as input_sdf:
162+
file_content = input_sdf.read()
163+
164+
# Strip relative path if requested
165+
if relative_to is not None:
166+
path = path.relative_to(relative_to)
167+
content.append(embed_sdf_content(str(path), file_content))
168+
return ",".join(content)
169+
170+
171+
def main(args=None) -> int:
172+
'''
173+
Main entrypoint
174+
'''
175+
if args is None:
176+
args = sys.argv[1:]
177+
178+
parser = argparse.ArgumentParser()
179+
180+
parser.add_argument(
181+
"--sdf-root",
182+
default=None,
183+
help="Directory containing sdf description files for each version",
184+
)
185+
parser.add_argument(
186+
"--input-files", nargs="*", help="List of input files to be embedded"
187+
)
188+
parser.add_argument(
189+
"--output-file", help="File to output embeddedsdf.cc content to"
190+
)
191+
192+
args = parser.parse_args(args)
193+
if not args.input_files or len(args.input_files) == 0:
194+
paths = collect_file_locations()
195+
else:
196+
paths = [Path(f) for f in args.input_files]
197+
content = generate_map_content(paths, args.sdf_root)
198+
write_output(content, args.output_file)
199+
return 0
200+
201+
202+
if __name__ == "__main__":
203+
sys.exit(main())

src/SDF_TEST.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include <gz/utils/Environment.hh>
2222
#include <gz/utils/SuppressWarning.hh>
2323

24+
#include "test_config.hh"
25+
#include "test_utils.hh"
26+
2427
#include "sdf/sdf.hh"
2528

2629
class SDFUpdateFixture
@@ -559,6 +562,59 @@ TEST(SDF, Version)
559562
EXPECT_STREQ(SDF_VERSION, sdf::SDF::Version().c_str());
560563
}
561564

565+
/////////////////////////////////////////////////
566+
TEST(SDF, EmbeddedSpec)
567+
{
568+
std::string result;
569+
570+
result = sdf::SDF::EmbeddedSpec("actor.sdf", false);
571+
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
572+
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
573+
std::string::npos);
574+
result = sdf::SDF::EmbeddedSpec("actor.sdf", true);
575+
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
576+
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
577+
std::string::npos);
578+
579+
result = sdf::SDF::EmbeddedSpec("root.sdf", false);
580+
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
581+
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
582+
result = sdf::SDF::EmbeddedSpec("root.sdf", true);
583+
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
584+
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
585+
}
586+
587+
TEST(SDF, EmbeddedSpecNonExistent)
588+
{
589+
std::string result;
590+
591+
// Capture sdferr output
592+
std::stringstream stderr_buffer;
593+
sdf::testing::RedirectConsoleStream redir(
594+
sdf::Console::Instance()->GetMsgStream(), &stderr_buffer);
595+
#ifdef _WIN32
596+
sdf::Console::Instance()->SetQuiet(false);
597+
sdf::testing::ScopeExit revertSetQuiet(
598+
[]
599+
{
600+
sdf::Console::Instance()->SetQuiet(true);
601+
});
602+
#endif
603+
604+
result = sdf::SDF::EmbeddedSpec("unavailable.sdf", false);
605+
EXPECT_NE(stderr_buffer.str().find("Unable to find SDF filename"),
606+
std::string::npos);
607+
EXPECT_NE(stderr_buffer.str().find("with version"), std::string::npos);
608+
EXPECT_TRUE(result.empty());
609+
610+
// clear the contents of the buffer
611+
stderr_buffer.str("");
612+
613+
result = sdf::SDF::EmbeddedSpec("unavailable.sdf", true);
614+
EXPECT_TRUE(stderr_buffer.str().empty());
615+
EXPECT_TRUE(result.empty());
616+
}
617+
562618
/////////////////////////////////////////////////
563619
TEST(SDF, FilePath)
564620
{

0 commit comments

Comments
 (0)