Skip to content

Configuration in header files - step 1 #1957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions tools/build_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ def build_project(src_path, build_path, target, toolchain_name,
prev_features = features
config.validate_config()

# And add the configuration macros to the toolchain
toolchain.add_macros(config.get_config_data_macros())
# Set the toolchain's config header with the config data
toolchain.set_config_header_content(config.get_config_data_header())

# Compile Sources
for path in src_paths:
Expand Down Expand Up @@ -403,8 +403,8 @@ def build_library(src_paths, build_path, target, toolchain_name,
prev_features = features
config.validate_config()

# And add the configuration macros to the toolchain
toolchain.add_macros(config.get_config_data_macros())
# Set the toolchain's config header with the config data
toolchain.set_config_header_content(config.get_config_data_header())

# Compile Sources
for path in src_paths:
Expand Down
55 changes: 51 additions & 4 deletions tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ def __init__(self, name, unit_name, unit_kind):
if len(tmp) != 2:
raise ValueError("Invalid macro definition '%s' in '%s'" % (name, self.defined_by))
self.macro_name = tmp[0]
self.macro_value = tmp[1]
else:
self.macro_name = name
self.macro_value = None

# 'Config' implements the mbed configuration mechanism
class Config:
Expand Down Expand Up @@ -343,13 +345,13 @@ def get_app_config_data(self, params, macros):

# Return the configuration data in two parts:
# - params: a dictionary with (name, ConfigParam) entries
# - macros: the list of macros defined with "macros" in libraries and in the application
# - macros: the list of macros defined with "macros" in libraries and in the application (as ConfigMacro instances)
def get_config_data(self):
all_params = self.get_target_config_data()
lib_params, macros = self.get_lib_config_data()
all_params.update(lib_params)
self.get_app_config_data(all_params, macros)
return all_params, [m.name for m in macros.values()]
return all_params, macros

# Helper: verify if there are any required parameters without a value in 'params'
def _check_required_parameters(self, params):
Expand All @@ -363,11 +365,17 @@ def _check_required_parameters(self, params):
def parameters_to_macros(params):
return ['%s=%s' % (m.macro_name, m.value) for m in params.values() if m.value is not None]

# Return the macro definitions generated for a dictionary of ConfigMacros (as returned by get_config_data)
# params: a dictionary of (name, ConfigMacro instance) mappings
@staticmethod
def config_macros_to_macros(macros):
return [m.name for m in macros.values()]

# Return the configuration data converted to a list of C macros
def get_config_data_macros(self):
params, macros = self.get_config_data()
self._check_required_parameters(params)
return macros + self.parameters_to_macros(params)
return self.config_macros_to_macros(macros) + self.parameters_to_macros(params)

# Returns any features in the configuration data
def get_features(self):
Expand All @@ -387,4 +395,43 @@ def validate_config(self):
if self.config_errors:
raise self.config_errors[0]
return True


# Return the configuration data converted to the content of a C header file,
# meant to be included to a C/C++ file. The content is returned as a string.
# If 'fname' is given, the content is also written to the file called "fname".
# WARNING: if 'fname' names an existing file, that file will be overwritten!
def get_config_data_header(self, fname = None):
params, macros = self.get_config_data()
self._check_required_parameters(params)
header_data = "// Automatically generated configuration file.\n"
header_data += "// DO NOT EDIT, content will be overwritten.\n\n"
header_data += "#ifndef __MBED_CONFIG_DATA__\n"
header_data += "#define __MBED_CONFIG_DATA__\n\n"
# Compute maximum length of macro names for proper alignment
max_param_macro_name_len = max([len(m.macro_name) for m in params.values() if m.value is not None]) if params else 0
max_direct_macro_name_len = max([len(m.macro_name) for m in macros.values()]) if macros else 0
max_macro_name_len = max(max_param_macro_name_len, max_direct_macro_name_len)
# Compute maximum length of macro values for proper alignment
max_param_macro_val_len = max([len(str(m.value)) for m in params.values() if m.value is not None]) if params else 0
max_direct_macro_val_len = max([len(m.macro_value or "") for m in macros.values()]) if macros else 0
max_macro_val_len = max(max_param_macro_val_len, max_direct_macro_val_len)
# Generate config parameters first
if params:
header_data += "// Configuration parameters\n"
for m in params.values():
if m.value is not None:
header_data += "#define {0:<{1}} {2!s:<{3}} // set by {4}\n".format(m.macro_name, max_macro_name_len, m.value, max_macro_val_len, m.set_by)
# Then macros
if macros:
header_data += "// Macros\n"
for m in macros.values():
if m.macro_value:
header_data += "#define {0:<{1}} {2!s:<{3}} // defined by {4}\n".format(m.macro_name, max_macro_name_len, m.macro_value, max_macro_val_len, m.defined_by)
else:
header_data += "#define {0:<{1}} // defined by {2}\n".format(m.macro_name, max_macro_name_len + max_macro_val_len + 1, m.defined_by)
header_data += "\n#endif\n"
# If fname is given, write "header_data" to it
if fname:
with open(fname, "wt") as f:
f.write(header_data)
return header_data
4 changes: 2 additions & 2 deletions tools/get_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
options.prefix = options.prefix or [""]

try:
params, macros = get_config(options.source_dir, target, toolchain)
params, macros, features = get_config(options.source_dir, target, toolchain)
if not params and not macros:
print "No configuration data available."
_exit(0)
Expand All @@ -79,7 +79,7 @@
print "Macros"
print "------"
if macros:
print 'Defined with "macros":', macros
print 'Defined with "macros":', Config.config_macros_to_macros(macros)
print "Generated from configuration parameters:", Config.parameters_to_macros(params)

except KeyboardInterrupt, e:
Expand Down
3 changes: 2 additions & 1 deletion tools/test/config_test/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

from tools.build_api import get_config
from tools.config import ConfigException
from tools.config import ConfigException, Config
import os, sys

# Compare the output of config against a dictionary of known good results
Expand Down Expand Up @@ -44,6 +44,7 @@ def test_tree(full_name, name):
err_msg = None
try:
cfg, macros, features = get_config(full_name, target, "GCC_ARM")
macros = Config.config_macros_to_macros(macros)
except ConfigException as e:
err_msg = e.message
if err_msg:
Expand Down
23 changes: 23 additions & 0 deletions tools/toolchains/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ def __init__(self, target, options=None, notify=None, macros=None, silent=False,

self.flags = deepcopy(self.DEFAULT_FLAGS)

# config_header_content will hold the content of the config header (if used)
self.config_header_content = None

def get_output(self):
return self.output

Expand Down Expand Up @@ -867,6 +870,26 @@ def mem_stats(self, map):
map_csv = splitext(map)[0] + "_map.csv"
memap.generate_output('csv-ci', map_csv)

# "Prefix headers" are automatically included by the compiler at the beginning of
# each source file. They are used to provide configuration data.
# header_content - the content of the config header file.
def set_config_header_content(self, header_content):
self.config_header_content = header_content

# Return the location of the config header. This function will create the config
# header first if needed. The header will be written in a file called "mbed_conf.h"
# located in the project's build directory.
# If config headers are not used (self.config_header_content is None), the function
# returns None
def get_config_header(self):
if self.config_header_content is None:
return None
config_file = join(self.build_dir, "mbed_conf.h")
if not exists(config_file):
with open(config_file, "wt") as f:
f.write(self.config_header_content)
return config_file


from tools.settings import ARM_BIN
from tools.settings import GCC_ARM_PATH, GCC_CR_PATH
Expand Down
6 changes: 5 additions & 1 deletion tools/toolchains/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ def get_dep_option(self, object):
return ["--depend", dep_path]

def get_compile_options(self, defines, includes):
return ['-D%s' % d for d in defines] + ['--via', self.get_inc_file(includes)]
opts = ['-D%s' % d for d in defines] + ['--via', self.get_inc_file(includes)]
config_header = self.get_config_header()
if config_header is not None:
opts = opts + ['--preinclude', config_header]
return opts

@hook_tool
def assemble(self, source, object, includes):
Expand Down
6 changes: 5 additions & 1 deletion tools/toolchains/gcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ def get_dep_option(self, object):
return ["-MD", "-MF", dep_path]

def get_compile_options(self, defines, includes):
return ['-D%s' % d for d in defines] + ['@%s' % self.get_inc_file(includes)]
opts = ['-D%s' % d for d in defines] + ['@%s' % self.get_inc_file(includes)]
config_header = self.get_config_header()
if config_header is not None:
opts = opts + ['-include', config_header]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-imacros might be better?
from the gnu gcc manual:

Exactly like -include, except that any output produced by scanning file is thrown away. Macros it defines remain defined. This allows you to acquire all the macros from a header without also processing its declarations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that changes anything?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the -include option does:

Process file as if #include "file" appeared as the first line of the primary source file. However, the first directory searched for file is the preprocessor's working directory instead of the directory containing the main source file. If not found there, it is searched for in the remainder of the #include "..." search chain as normal.

Copy link
Contributor

@theotherjimmy theotherjimmy Jun 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is that declarations in a -included file are heeded where as in a -imacrosed file they are not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant I don't see how that changes the current functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It enforces the restriction that the configuration be only preprocessor defines.

return opts

@hook_tool
def assemble(self, source, object, includes):
Expand Down
6 changes: 5 additions & 1 deletion tools/toolchains/iar.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ def cc_extra(self, object):
return ["-l", base + '.s.txt']

def get_compile_options(self, defines, includes):
return ['-D%s' % d for d in defines] + ['-f', self.get_inc_file(includes)]
opts = ['-D%s' % d for d in defines] + ['-f', self.get_inc_file(includes)]
config_header = self.get_config_header()
if config_header is not None:
opts = opts + ['--preinclude', config_header]
return opts

@hook_tool
def assemble(self, source, object, includes):
Expand Down