Skip to content

Commit c897762

Browse files
committed
Add a "single" source/header file generation and build mode
Introduce a new way of building: before any file is compiled all source files are combined into a single C file and all header files into a single H file (per subdir). This new approach makes it possible to quickly integrate JerryScript into other projects: ``` $ gcc -o demo demo.c jerryscript.c jerryscript-port-default.c -lm ``` To use the source generator run: ``` $ cmake -Bbuild_dir -H. -DENABLE_ALL_IN_ONE_SOURCE=ON $ make -C build_dir generate-single-source ``` This will create the following files in the `build_dir`: * jerryscript.c * jerryscript.h * jerryscript-config.h * jerryscript-port-default.c * jerryscript-port-default.h JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.u-szeged@partner.samsung.com
1 parent ce260ab commit c897762

File tree

4 files changed

+336
-0
lines changed

4 files changed

+336
-0
lines changed

jerry-core/CMakeLists.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,56 @@ if(ENABLE_ALL_IN_ONE)
150150
set(SOURCE_CORE_FILES ${ALL_IN_FILE})
151151
endif()
152152

153+
# "Single" JerryScript source/header build.
154+
# The process will create the following files:
155+
# * jerryscript.c
156+
# * jerryscript.h
157+
# * jerryscript-config.h
158+
if(ENABLE_ALL_IN_ONE_SOURCE)
159+
# Create a default configuration
160+
set(JERRYSCRIPT_CONFIG_H "${CMAKE_BINARY_DIR}/jerryscript-config.h")
161+
set(JERRYSCRIPT_SOURCE_CONFIG_H "${CMAKE_CURRENT_SOURCE_DIR}/config.h")
162+
add_custom_command(OUTPUT ${JERRYSCRIPT_CONFIG_H}
163+
COMMAND ${CMAKE_COMMAND} -E copy ${JERRYSCRIPT_SOURCE_CONFIG_H} ${JERRYSCRIPT_CONFIG_H}
164+
DEPENDS ${JERRYSCRIPT_SOURCE_CONFIG_H})
165+
166+
# Create single C file
167+
file(GLOB HEADER_CORE_FILES *.h)
168+
set(ALL_IN_FILE "${CMAKE_BINARY_DIR}/jerryscript.c")
169+
set(ALL_IN_FILE_H "${CMAKE_BINARY_DIR}/jerryscript.h")
170+
add_custom_command(OUTPUT ${ALL_IN_FILE}
171+
COMMAND python ${CMAKE_SOURCE_DIR}/tools/srcmerger.py
172+
--base-dir ${CMAKE_CURRENT_SOURCE_DIR}
173+
--input ${CMAKE_CURRENT_SOURCE_DIR}/api/jerry.c
174+
--output ${ALL_IN_FILE}
175+
--append-c-files
176+
--remove-include jerryscript.h
177+
--remove-include jerryscript-port.h
178+
--remove-include jerryscript-compiler.h
179+
--remove-include jerryscript-core.h
180+
--remove-include jerryscript-debugger.h
181+
--remove-include jerryscript-debugger-transport.h
182+
--remove-include jerryscript-port.h
183+
--remove-include jerryscript-snapshot.h
184+
--remove-include config.h
185+
--push-include jerryscript.h
186+
DEPENDS ${SOURCE_CORE_FILES} ${ALL_IN_FILE_H} ${JERRYSCRIPT_CONFIG_H}
187+
)
188+
add_custom_command(OUTPUT ${ALL_IN_FILE_H}
189+
COMMAND python ${CMAKE_SOURCE_DIR}/tools/srcmerger.py
190+
--base-dir ${CMAKE_CURRENT_SOURCE_DIR}
191+
--input ${CMAKE_CURRENT_SOURCE_DIR}/include/jerryscript.h
192+
--output ${ALL_IN_FILE_H}
193+
--remove-include config.h
194+
--push-include jerryscript-config.h
195+
DEPENDS ${HEADER_CORE_FILES} ${JERRYSCRIPT_CONFIG_H}
196+
)
197+
add_custom_target(generate-single-source-jerry DEPENDS ${ALL_IN_FILE} ${ALL_IN_FILE_H})
198+
add_custom_target(generate-single-source DEPENDS generate-single-source-jerry)
199+
200+
set(SOURCE_CORE_FILES ${ALL_IN_FILE} ${ALL_IN_FILE_H})
201+
endif()
202+
153203
# Third-party
154204
# Valgrind
155205
set(INCLUDE_THIRD_PARTY_VALGRIND "${CMAKE_SOURCE_DIR}/third-party/valgrind")

jerry-port/default/CMakeLists.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,43 @@ set(INCLUDE_PORT_DEFAULT "${CMAKE_CURRENT_SOURCE_DIR}/include")
2222
# Source directories
2323
file(GLOB SOURCE_PORT_DEFAULT *.c)
2424

25+
# "Single" JerryScript source/header build.
26+
# The process will create the following files:
27+
# * jerryscript-port-default.c
28+
# * jerryscript-port-default.h
29+
if(ENABLE_ALL_IN_ONE_SOURCE)
30+
file(GLOB HEADER_PORT_DEFAULT *.h)
31+
set(ALL_IN_FILE "${CMAKE_BINARY_DIR}/jerryscript-port-default.c")
32+
set(ALL_IN_FILE_H "${CMAKE_BINARY_DIR}/jerryscript-port-default.h")
33+
add_custom_command(OUTPUT ${ALL_IN_FILE}
34+
COMMAND python ${CMAKE_SOURCE_DIR}/tools/srcmerger.py
35+
--base-dir ${CMAKE_CURRENT_SOURCE_DIR}
36+
--output ${ALL_IN_FILE}
37+
--append-c-files
38+
--remove-include jerryscript-port.h
39+
--remove-include jerryscript-port-default.h
40+
--remove-include jerryscript-debugger.h
41+
--push-include jerryscript.h
42+
--push-include jerryscript-port-default.h
43+
DEPENDS ${SOURCE_PORT_DEFAULT} ${ALL_IN_FILE_H}
44+
)
45+
46+
add_custom_command(OUTPUT ${ALL_IN_FILE_H}
47+
COMMAND python ${CMAKE_SOURCE_DIR}/tools/srcmerger.py
48+
--base-dir ${CMAKE_CURRENT_SOURCE_DIR}/
49+
--input ${CMAKE_CURRENT_SOURCE_DIR}/include/jerryscript-port-default.h
50+
--output ${ALL_IN_FILE_H}
51+
--remove-include jerryscript-port.h
52+
--remove-include jerryscript.h
53+
--push-include jerryscript.h
54+
DEPENDS ${HEADER_PORT_DEFAULT}
55+
)
56+
add_custom_target(generate-single-source-port DEPENDS ${ALL_IN_FILE} ${ALL_IN_FILE_H})
57+
add_dependencies(generate-single-source generate-single-source-port)
58+
59+
set(SOURCE_PORT_DEFAULT ${ALL_IN_FILE} ${ALL_IN_FILE_H})
60+
endif()
61+
2562
# Define _BSD_SOURCE and _DEFAULT_SOURCE
2663
# (should only be necessary if we used compiler default libc but not checking that)
2764
set(DEFINES_PORT_DEFAULT _BSD_SOURCE _DEFAULT_SOURCE)

tools/run-tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ def skip_if(condition, desc):
160160
['--regexp-recursion-limit=1000']),
161161
Options('buildoption_test-vm_recursion_limit',
162162
OPTIONS_VM_RECURSION_LIMIT),
163+
Options('buildoption_test-single-source',
164+
['--cmake-param=-DENABLE_ALL_IN_ONE_SOURCE=ON']),
163165
]
164166

165167
def get_arguments():

tools/srcmerger.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright JS Foundation and other contributors, http://js.foundation
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
from __future__ import print_function
17+
18+
import argparse
19+
import fnmatch
20+
import logging
21+
import os
22+
import re
23+
import sys
24+
25+
26+
_COPYRIGHT_TEMPLATE = """/* Copyright JS Foundation and other contributors, http://js.foundation
27+
*
28+
* Licensed under the Apache License, Version 2.0 (the "License");
29+
* you may not use this file except in compliance with the License.
30+
* You may obtain a copy of the License at
31+
*
32+
* http://www.apache.org/licenses/LICENSE-2.0
33+
*
34+
* Unless required by applicable law or agreed to in writing, software
35+
* distributed under the License is distributed on an "AS IS" BASIS
36+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37+
* See the License for the specific language governing permissions and
38+
* limitations under the License.
39+
*/
40+
"""
41+
42+
class SourceMerger(object):
43+
44+
_RE_INCLUDE = re.compile(r'\s*#include ("|<)(.*?)("|>)\n$')
45+
46+
def __init__(self, h_files, c_files, extra_includes=None, remove_includes=None):
47+
self._log = logging.getLogger(__name__ + "." + self.__class__.__name__)
48+
self._last_builtin = None
49+
self._processed = []
50+
self._output = []
51+
self._file_level = 0
52+
self._h_files = h_files
53+
self._c_files = c_files
54+
self._extra_includes = extra_includes
55+
self._remove_includes = remove_includes
56+
57+
def _process_non_include(self, line):
58+
# Special case #2: Builtin include header name usage
59+
if line.strip() == "#include BUILTIN_INC_HEADER_NAME":
60+
assert self._last_builtin is not None, 'No previous BUILTIN_INC_HEADER_NAME definition'
61+
self._log.debug('[%d] Detected usage of BUILTIN_INC_HEADER_NAME, including: %s',
62+
self._file_level, self._last_builtin)
63+
self.add_file(self._h_files[self._last_builtin])
64+
# return from the function as we have processed the included file
65+
return
66+
67+
# Special case #1: Builtin include header name definition
68+
if line.startswith('#define BUILTIN_INC_HEADER_NAME '):
69+
# the line is in this format: #define BUILTIN_INC_HEADER_NAME "<filename>"
70+
self._last_builtin = line.split('"', 2)[1]
71+
self._log.debug('[%d] Detected definition of BUILTIN_INC_HEADER_NAME: %s',
72+
self._file_level, self._last_builtin)
73+
74+
# the line is not anything special, just push it into the output
75+
self._output.append(line)
76+
77+
def add_file(self, filename):
78+
if os.path.basename(filename) in self._processed:
79+
self._log.warning('Tried to to process an already processed file: "%s"', filename)
80+
return
81+
82+
self._file_level += 1
83+
84+
# mark the start of the new file in the output
85+
self._output.append('#line 1 "%s"\n' % (filename))
86+
87+
line_idx = 0
88+
with open(filename, 'r') as input_file:
89+
in_copyright = False
90+
for line in input_file:
91+
line_idx += 1
92+
93+
if not in_copyright and line.startswith('/* Copyright '):
94+
in_copyright = True
95+
continue
96+
97+
if in_copyright:
98+
if line.strip().endswith('*/'):
99+
in_copyright = False
100+
101+
continue
102+
103+
# check if the line is an '#include' line
104+
match = SourceMerger._RE_INCLUDE.match(line)
105+
if not match:
106+
# the line is not a header
107+
self._process_non_include(line)
108+
continue
109+
110+
if match.group(1) == '<':
111+
# found a "global" include
112+
self._output.append(line)
113+
continue
114+
115+
name = match.group(2)
116+
117+
if name in self._remove_includes:
118+
self._log.debug('[%d] Removing include line (%s:%d): %s',
119+
self._file_level, filename, line_idx, line.strip())
120+
continue
121+
122+
if name not in self._h_files:
123+
self._log.warning('[%d] Include not found: "%s" in "%s:%d"',
124+
self._file_level, name, filename, line_idx)
125+
self._output.append(line)
126+
continue
127+
128+
if name in self._processed:
129+
self._log.debug('[%d] Already included: "%s"',
130+
self._file_level, name)
131+
continue
132+
133+
self._log.debug('[%d] Including: "%s"',
134+
self._file_level, self._h_files[name])
135+
self.add_file(self._h_files[name])
136+
137+
# mark the continuation of the current file in the output
138+
self._output.append('#line %d "%s"\n' % (line_idx + 1, filename))
139+
140+
if not name.endswith('.inc.h'):
141+
# if the included file is not a "*.inc.h" file mark it as processed
142+
self._processed.append(name)
143+
144+
self._file_level -= 1
145+
if not filename.endswith('.inc.h'):
146+
self._processed.append(os.path.basename(filename))
147+
148+
def write_output(self, out_fp):
149+
out_fp.write(_COPYRIGHT_TEMPLATE)
150+
151+
if self._extra_includes:
152+
for include in self._extra_includes:
153+
out_fp.write('#include "%s"\n' % include)
154+
155+
for line in self._output:
156+
out_fp.write(line)
157+
158+
159+
def match_files(base_dir, pattern):
160+
161+
for path, subdirs, files in os.walk(base_dir):
162+
for name in files:
163+
if fnmatch.fnmatch(name, pattern):
164+
yield os.path.join(path, name)
165+
166+
167+
def collect_files(base_dir, pattern):
168+
"""
169+
Collect files in the provided base directory given a file pattern.
170+
Will collect all files in the base dir recursively.
171+
172+
:param base_dir: directory to search in
173+
:param pattern: file patterh to use
174+
:returns dictionary: a dictionary file base name -> file path mapping
175+
"""
176+
177+
#files = glob.glob(os.path.join(base_dir, '**', pattern), recursive=True)
178+
name_mapping = {}
179+
for fname in match_files(base_dir, pattern):
180+
name = os.path.basename(fname)
181+
182+
if name in name_mapping:
183+
print('Duplicate name detected: "%s" and "%s"' % (name, name_mapping[name]))
184+
continue
185+
186+
name_mapping[name] = fname
187+
188+
return name_mapping
189+
190+
191+
def run_merger(args):
192+
h_files = collect_files(args.base_dir, '*.h')
193+
c_files = collect_files(args.base_dir, '*.c')
194+
195+
for name in args.remove_include:
196+
c_files.pop(name, '')
197+
h_files.pop(name, '')
198+
199+
merger = SourceMerger(h_files, c_files, args.push_include, args.remove_include)
200+
if args.input_file:
201+
merger.add_file(args.input_file)
202+
203+
if args.append_c_files:
204+
# if the input file is in the C files list it should be removed to avoid
205+
# double inclusion of the file
206+
if args.input_file:
207+
input_name = os.path.basename(args.input_file)
208+
c_files.pop(input_name, '')
209+
210+
# Add the C files in reverse the order to make sure that builtins are
211+
# not at the beginning.
212+
for name, fname in sorted(c_files.items(), reverse=True):
213+
merger.add_file(fname)
214+
215+
with open(args.output_file, 'w') as output:
216+
merger.write_output(output)
217+
218+
219+
def main():
220+
parser = argparse.ArgumentParser(description='Merge source/header files.')
221+
parser.add_argument('--base-dir', metavar='DIR', type=str, dest='base_dir',
222+
help='', default=os.path.curdir)
223+
parser.add_argument('--input', metavar='FILE', type=str, dest='input_file',
224+
help='Main input source/header file')
225+
parser.add_argument('--output', metavar='FILE', type=str, dest='output_file',
226+
help='Output source/header file')
227+
parser.add_argument('--append-c-files', dest='append_c_files', default=False,
228+
action='store_true', help='das')
229+
parser.add_argument('--remove-include', action='append', default=[])
230+
parser.add_argument('--push-include', action='append', default=[])
231+
parser.add_argument('--verbose', '-v', action='store_true', default=False)
232+
233+
args = parser.parse_args()
234+
235+
log_level = logging.WARNING
236+
if args.verbose:
237+
log_level = logging.DEBUG
238+
239+
logging.basicConfig(level=log_level)
240+
logger = logging.getLogger(__name__)
241+
logger.debug('Starting merge with args: %s', str(sys.argv))
242+
243+
run_merger(args)
244+
245+
246+
if __name__ == "__main__":
247+
main()

0 commit comments

Comments
 (0)