Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 13: [PATCH 13/17] fix(build): preserve Homebrew Boost system header classification

ci/test/test_homebrew_boost_headers.py

Before: /dev/null · After: ci/test/test_homebrew_boost_headers.py

File metadata
new file mode 100644
index 0000000000000000000000000000000000000000..ca865a7a27303ae437d6ff07be70ea814cf69e5e

Download original patch · Release notes

Diff rows 1–105 of 105. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

ci/test/test_homebrew_boost_headers.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2026-present The Bitcoin Roots developers
3
+ # Distributed under the MIT software license, see the accompanying
4
+ # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
+
6
+ from pathlib import Path
7
+ import os
8
+ import shlex
9
+ import shutil
10
+ import subprocess
11
+ import tempfile
12
+ import unittest
13
+
14
+
15
+ ROOT = Path(__file__).resolve().parents[2]
16
+ CLANG = shutil.which('clang++')
17
+
18
+
19
+ @unittest.skipUnless(CLANG and shutil.which('cmake'), 'CMake and Clang are required')
20
+ class HomebrewBoostHeadersTest(unittest.TestCase):
21
+ def build(self, *, apple=True, matching=True, project_warning=False):
22
+ with tempfile.TemporaryDirectory(prefix='roots-homebrew-boost-') as directory:
23
+ work = Path(directory)
24
+ cellar = work / 'Cellar/boost/1.92.0'
25
+ headers = cellar / 'include/boost'
26
+ headers.mkdir(parents=True)
27
+ header = ('struct Base { virtual void lock() {} };\n'
28
+ 'template<class T> struct Derived: Base { void lock() {} };\n')
29
+ (headers / 'signal.hpp').write_text(header)
30
+ (headers / 'config.hpp').write_text('')
31
+ prefix = work / 'homebrew'
32
+ (prefix / 'include').mkdir(parents=True)
33
+ if matching:
34
+ (prefix / 'include/boost').symlink_to(headers, target_is_directory=True)
35
+ else:
36
+ # A different installation must not gain system classification.
37
+ (prefix / 'include/boost').mkdir()
38
+ (prefix / 'include/boost/signal.hpp').write_text(header)
39
+ config = cellar / 'lib/cmake/Boost-1.92.0'
40
+ config.mkdir(parents=True)
41
+ (config / 'BoostConfig.cmake').write_text(
42
+ 'add_library(Boost::headers INTERFACE IMPORTED)\n'
43
+ 'set_target_properties(Boost::headers PROPERTIES\n'
44
+ f' INTERFACE_INCLUDE_DIRECTORIES "{cellar / "include"}")\n')
45
+ (config / 'BoostConfigVersion.cmake').write_text(
46
+ 'set(PACKAGE_VERSION "1.92.0")\n'
47
+ 'set(PACKAGE_VERSION_COMPATIBLE TRUE)\n')
48
+ tools = work / 'tools'
49
+ tools.mkdir()
50
+ brew = tools / 'brew'
51
+ brew.write_text('#!/bin/sh\ncase "$*" in\n'
52
+ f' "--prefix boost") printf "%s\\n" {shlex.quote(str(cellar))} ;;\n'
53
+ f' "--prefix") printf "%s\\n" {shlex.quote(str(prefix))} ;;\n'
54
+ ' *) exit 1 ;;\nesac\n')
55
+ brew.chmod(0o755)
56
+ source = work / 'source'
57
+ source.mkdir()
58
+ (source / 'main.cpp').write_text(
59
+ '#include <boost/signal.hpp>\nDerived<int> item;\n'
60
+ + ('struct Own: Base { void lock() {} };\nOwn own;\n' if project_warning else ''))
61
+ (source / 'CMakeLists.txt').write_text(
62
+ 'cmake_minimum_required(VERSION 3.22)\n'
63
+ 'project(HomebrewBoostHeaders LANGUAGES CXX)\n'
64
+ f'set(CMAKE_HOST_APPLE {"TRUE" if apple else "FALSE"})\n'
65
+ f'include("{ROOT / "cmake/module/AddBoostIfNeeded.cmake"}")\n'
66
+ 'add_boost_if_needed()\n'
67
+ 'add_library(probe OBJECT main.cpp)\n'
68
+ 'target_link_libraries(probe PRIVATE Boost::headers)\n'
69
+ 'target_compile_options(probe PRIVATE -Wsuggest-override -Werror)\n')
70
+ # Model an earlier linked-prefix search path without changing the
71
+ # configured Boost dependency. This does not assert runner CPATH.
72
+ environment = {**os.environ, 'PATH': f'{tools}:{os.environ["PATH"]}',
73
+ 'CPATH': str(prefix / 'include')}
74
+ build = work / 'build'
75
+ configure = subprocess.run(
76
+ ['cmake', '-S', str(source), '-B', str(build),
77
+ f'-DCMAKE_CXX_COMPILER={CLANG}', f'-DBoost_DIR={config}'],
78
+ capture_output=True, text=True, env=environment)
79
+ self.assertEqual(configure.returncode, 0, configure.stdout + configure.stderr)
80
+ return subprocess.run(['cmake', '--build', str(build), '--parallel', '2'],
81
+ capture_output=True, text=True, env=environment)
82
+
83
+ def test_matching_linked_headers_keep_system_classification(self):
84
+ result = self.build()
85
+ self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
86
+
87
+ def test_project_override_warnings_still_fail(self):
88
+ result = self.build(project_warning=True)
89
+ self.assertNotEqual(result.returncode, 0)
90
+ self.assertIn('main.cpp:', result.stderr)
91
+ self.assertIn('[-Werror,-Wsuggest-override]', result.stderr)
92
+
93
+ def test_different_boost_installation_is_not_hidden(self):
94
+ result = self.build(matching=False)
95
+ self.assertNotEqual(result.returncode, 0)
96
+ self.assertIn('homebrew/include/boost/signal.hpp:', result.stderr)
97
+
98
+ def test_other_hosts_do_not_classify_homebrew_alias(self):
99
+ result = self.build(apple=False)
100
+ self.assertNotEqual(result.returncode, 0)
101
+ self.assertIn('homebrew/include/boost/signal.hpp:', result.stderr)
102
+
103
+
104
+ if __name__ == '__main__':
105
+ unittest.main()