summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--zephyr/CMakeLists.txt30
-rw-r--r--zephyr/zmake/tests/test_util.py15
-rw-r--r--zephyr/zmake/tests/test_version.py81
-rw-r--r--zephyr/zmake/tests/test_zmake.py3
-rw-r--r--zephyr/zmake/zmake/util.py23
-rw-r--r--zephyr/zmake/zmake/version.py43
-rw-r--r--zephyr/zmake/zmake/zmake.py12
7 files changed, 180 insertions, 27 deletions
diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt
index af577cdc34..171ab973d2 100644
--- a/zephyr/CMakeLists.txt
+++ b/zephyr/CMakeLists.txt
@@ -25,33 +25,11 @@ if(NOT EXISTS "${PLATFORM_EC}/zephyr/module.yml")
assert_exists("${PLATFORM_EC}/zephyr/module.yml")
endif()
-if(DEFINED CONFIG_PLATFORM_EC)
- # Build the EC's expected ec_version.h file.
- set(ec_version_file "${CMAKE_BINARY_DIR}/ec/include/generated/ec_version.h")
- # Create the command used to generate the ec_version.h file.
- add_custom_command(
- OUTPUT ${ec_version_file}
- # TODO(b/185249526): zephyr: add project name to the version output
- COMMAND BOARD=${BOARD}_zephyr ${PLATFORM_EC}/util/getversion.sh > ${ec_version_file}
- WORKING_DIRECTORY ${PLATFORM_EC}
- )
- # Create a custom target that will depend on the file.
- add_custom_target(generate_ec_version DEPENDS ${ec_version_file})
-
- # Create a local library (ec_version) that will be a pure interface library
- # and will depend on the custom target that generates the ec_version.h file.
- add_library(ec_version INTERFACE)
- add_dependencies(ec_version generate_ec_version)
-
- # Ensure the EC version file is generated before trying to build the app
- # library, which includes compiling version.c
- add_dependencies(app ec_version)
-
- # Register the library with zephyr so that it generates the file at build
- # time. Also, append the include directory so we can include ec_version.h.
- zephyr_append_cmake_library(ec_version)
- zephyr_include_directories("${CMAKE_BINARY_DIR}/ec/include/generated")
+if(DEFINED ZMAKE_INCLUDE_DIR)
+ zephyr_include_directories("${ZMAKE_INCLUDE_DIR}")
+endif()
+if(DEFINED CONFIG_PLATFORM_EC)
# Add CHROMIUM_EC definition, which is used by ec_commands.h to
# determine that the header is being compiled for the EC instead of
# by another third-party C codebase.
diff --git a/zephyr/zmake/tests/test_util.py b/zephyr/zmake/tests/test_util.py
index 15f92f58ab..0524a153fd 100644
--- a/zephyr/zmake/tests/test_util.py
+++ b/zephyr/zmake/tests/test_util.py
@@ -87,3 +87,18 @@ def test_read_kconfig_autoconf_value(value):
f.write("#define TEST {}".format(value))
read_value = util.read_kconfig_autoconf_value(path, "TEST")
assert int(read_value) == value
+
+
+@pytest.mark.parametrize(
+ ["input_str", "expected_result"],
+ [
+ ("", '""'),
+ ("TROGDOR ABC-123", '"TROGDOR ABC-123"'),
+ ("hello world", '"hello world"'),
+ ("hello\nworld", r'"hello\nworld"'),
+ ('hello"world', r'"hello\"world"'),
+ ("hello\\world", '"hello\\\\world"'),
+ ],
+)
+def test_c_str(input_str, expected_result):
+ assert util.c_str(input_str) == expected_result
diff --git a/zephyr/zmake/tests/test_version.py b/zephyr/zmake/tests/test_version.py
index 9e99ff09fb..4407e52642 100644
--- a/zephyr/zmake/tests/test_version.py
+++ b/zephyr/zmake/tests/test_version.py
@@ -2,7 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import datetime
import subprocess
+import unittest.mock as mock
+
+import pytest
import zmake.project
import zmake.version as version
@@ -96,3 +100,80 @@ def test_version_string_static(tmp_path):
version.get_version_string(project, zephyr_base, modules, static=True)
== "prj_v2.6.0-STATIC"
)
+
+
+@pytest.fixture
+def fake_user_hostname():
+ with mock.patch("getpass.getuser", return_value="toukmond", autospec=True):
+ with mock.patch("platform.node", return_value="pokey", autospec=True):
+ yield
+
+
+@pytest.fixture
+def fake_date():
+ fixed_date = datetime.datetime(2021, 6, 28, 3, 18, 53)
+ with mock.patch("datetime.datetime") as mock_datetime:
+ mock_datetime.now.return_value = fixed_date
+ yield
+
+
+HEADER_VERSION_STR = "trogdor_v2.6.1004-cmsis:0dead0,hal_stm32:0beef0,os:ad00da"
+EXPECTED_HEADER = (
+ "/* This file is automatically generated by zmake */\n"
+ '#define VERSION "trogdor_v2.6.1004-cmsis:0dead0,hal_stm32:0beef0,os:ad00da"\n'
+ '#define CROS_EC_VERSION32 "trogdor_v2.6.1004-cmsis:0dead0,"\n'
+ '#define BUILDER "toukmond@pokey"\n'
+ '#define DATE "2021-06-28 03:18:53"\n'
+)
+HEADER_VERSION_STR_STATIC = "trogdor_v2.6.0-STATIC"
+EXPECTED_HEADER_STATIC = (
+ "/* This file is automatically generated by zmake */\n"
+ '#define VERSION "trogdor_v2.6.0-STATIC"\n'
+ '#define CROS_EC_VERSION32 "trogdor_v2.6.0-STATIC"\n'
+ '#define BUILDER "reproducible@build"\n'
+ '#define DATE "STATIC_VERSION_DATE"\n'
+)
+
+
+def test_header_gen(fake_user_hostname, fake_date, tmp_path):
+ # Test the simple case (static=False, no existing header).
+ output_file = tmp_path / "ec_version.h"
+ version.write_version_header(HEADER_VERSION_STR, output_file)
+ assert output_file.read_text() == EXPECTED_HEADER
+
+
+def test_header_gen_reproducible_build(tmp_path):
+ # With static=True this time.
+ output_file = tmp_path / "ec_version.h"
+ version.write_version_header(HEADER_VERSION_STR_STATIC, output_file, static=True)
+ assert output_file.read_text() == EXPECTED_HEADER_STATIC
+
+
+def test_header_gen_exists_not_changed(fake_user_hostname, fake_date, tmp_path):
+ # Test we don't overwrite if no changes needed.
+ output_file = tmp_path / "ec_version.h"
+
+ # First time, write and record mtime.
+ version.write_version_header(HEADER_VERSION_STR, output_file)
+ expected_mtime = output_file.stat().st_mtime
+
+ # Do another write (contents should be unchanged).
+ version.write_version_header(HEADER_VERSION_STR, output_file)
+
+ # Assert we didn't write again.
+ assert output_file.stat().st_mtime == expected_mtime
+
+
+def test_header_gen_exists_needs_changes(fake_user_hostname, fake_date, tmp_path):
+ # Test we overwrite when it exists already and changes are needed.
+ output_file = tmp_path / "ec_version.h"
+
+ # First time, write and save contents.
+ version.write_version_header(HEADER_VERSION_STR, output_file)
+ original_contents = output_file.read_text()
+
+ # Do another write (contents should be changed).
+ version.write_version_header(HEADER_VERSION_STR_STATIC, output_file, static=True)
+
+ # Assert we overwrote.
+ assert output_file.read_text() != original_contents
diff --git a/zephyr/zmake/tests/test_zmake.py b/zephyr/zmake/tests/test_zmake.py
index a5c132dbaa..f7978764f8 100644
--- a/zephyr/zmake/tests/test_zmake.py
+++ b/zephyr/zmake/tests/test_zmake.py
@@ -141,7 +141,8 @@ EXTRAVERSION =
pathlib.Path(tmpname), build_dir=pathlib.Path("build")
)
else:
- zmk.build(pathlib.Path(tmpname))
+ with patch("zmake.version.write_version_header", autospec=True):
+ zmk.build(pathlib.Path(tmpname))
multiproc.wait_for_log_end()
recs = [rec.getMessage() for rec in cap.records]
diff --git a/zephyr/zmake/zmake/util.py b/zephyr/zmake/zmake/util.py
index 18d03dd7f0..455cb7c9d6 100644
--- a/zephyr/zmake/zmake/util.py
+++ b/zephyr/zmake/zmake/util.py
@@ -9,6 +9,29 @@ import re
import shlex
+def c_str(input_str):
+ """Make a string that can be included as a literal in C source code.
+
+ Args:
+ input_str: The string to process.
+
+ Returns:
+ A string which can be included in C source code.
+ """
+
+ def c_chr(char):
+ # Convert a char in a string to the C representation. Per the
+ # C standard, we can use all characters but quote, newline,
+ # and backslash directly with no replacements.
+ return {
+ '"': r"\"",
+ "\n": r"\n",
+ "\\": "\\\\",
+ }.get(char, char)
+
+ return '"{}"'.format("".join(map(c_chr, input_str)))
+
+
def locate_cros_checkout():
"""Find the path to the ChromiumOS checkout.
diff --git a/zephyr/zmake/zmake/version.py b/zephyr/zmake/zmake/version.py
index d026c07c2f..5312c8697e 100644
--- a/zephyr/zmake/zmake/version.py
+++ b/zephyr/zmake/zmake/version.py
@@ -2,7 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import datetime
+import getpass
+import io
import os
+import platform
import subprocess
import zmake.util as util
@@ -108,3 +112,42 @@ def get_version_string(project, zephyr_base, modules, static=False):
return "{}_v{}.{}.{}-{}".format(
project_id, major_version, minor_version, num_commits, vcs_hashes
)
+
+
+def write_version_header(version_str, output_path, static=False):
+ """Generate a version header and write it to the specified path.
+
+ Generate a version header in the format expected by the EC build
+ system, and write it out only if the version header does not exist
+ or changes. We don't write in the case that the version header
+ does exist and was unchanged, which allows "zmake build" commands
+ on an unchanged tree to be an effective no-op.
+
+ Args:
+ version_str: The version string to be used in the header, such
+ as one generated by get_version_string.
+ output_path: The file path to write at (a pathlib.Path
+ object).
+ static: If true, generate a header which does not include
+ information like the username, hostname, or date, allowing
+ the build to be reproducible.
+ """
+ output = io.StringIO()
+ output.write("/* This file is automatically generated by zmake */\n")
+
+ def add_def(name, value):
+ output.write("#define {} {}\n".format(name, util.c_str(value)))
+
+ add_def("VERSION", version_str)
+ add_def("CROS_EC_VERSION32", version_str[:31])
+
+ if static:
+ add_def("BUILDER", "reproducible@build")
+ add_def("DATE", "STATIC_VERSION_DATE")
+ else:
+ add_def("BUILDER", "{}@{}".format(getpass.getuser(), platform.node()))
+ add_def("DATE", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
+
+ contents = output.getvalue()
+ if not output_path.exists() or output_path.read_text() != contents:
+ output_path.write_text(contents)
diff --git a/zephyr/zmake/zmake/zmake.py b/zephyr/zmake/zmake/zmake.py
index 45e23fd904..0d3dbb6450 100644
--- a/zephyr/zmake/zmake/zmake.py
+++ b/zephyr/zmake/zmake/zmake.py
@@ -235,6 +235,7 @@ class Zmake:
self.logger.info("Clearing old build directory %s", build_dir)
shutil.rmtree(build_dir)
+ generated_include_dir = build_dir / "include"
base_config = zmake.build_config.BuildConfig(
environ_defs={"ZEPHYR_BASE": str(zephyr_base), "PATH": "/usr/bin"},
cmake_defs={
@@ -242,6 +243,7 @@ class Zmake:
"SYSCALL_INCLUDE_DIRS": str(
self.module_paths["ec"] / "zephyr" / "include" / "drivers"
),
+ "ZMAKE_INCLUDE_DIR": str(generated_include_dir),
},
)
@@ -274,6 +276,8 @@ class Zmake:
if not build_dir.exists():
build_dir = build_dir.mkdir()
+ if not generated_include_dir.exists():
+ generated_include_dir.mkdir()
processes = []
self.logger.info("Building %s in %s.", project_dir, build_dir)
for build_name, build_config in project.iter_builds():
@@ -375,6 +379,14 @@ class Zmake:
zmake.modules.locate_from_directory(build_dir / "modules"),
)
+ # The version header needs to generated during the build phase
+ # instead of configure, as the tree may have changed since
+ # configure was run.
+ zmake.version.write_version_header(
+ version_string,
+ build_dir / "include" / "ec_version.h",
+ )
+
for build_name, build_config in project.iter_builds():
with self.jobserver.get_job():
dirs[build_name] = build_dir / "build-{}".format(build_name)