Skip to content
Open
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
19 changes: 8 additions & 11 deletions utils/build.config
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
[component]
component=daos
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is holdover from when scons_local supported cart, daos, cppr, and iof independently


[commit_versions]
argobots=v1.2
fused=v1.0.0
pmdk=2.1.3
isal=v2.31.1
isal_crypto=v2.25.0
spdk=v24.09
ofi=v1.22.0
mercury=v2.4.1
ofi=v1.22.0
pmdk=2.1.3
protobufc=v1.3.3
spdk=v24.09
ucx=v1.14.1

[repos]
argobots=https://github.com/pmodels/argobots.git
fused=https://github.com/daos-stack/fused.git
pmdk=https://github.com/daos-stack/pmdk.git
isal=https://github.com/intel/isa-l.git
isal_crypto=https://github.com/intel/isa-l_crypto.git
spdk=https://github.com/spdk/spdk.git
ofi=https://github.com/ofiwg/libfabric.git
mercury=https://github.com/mercury-hpc/mercury.git
ofi=https://github.com/ofiwg/libfabric.git
pmdk=https://github.com/daos-stack/pmdk.git
protobufc=https://github.com/protobuf-c/protobuf-c.git
spdk=https://github.com/spdk/spdk.git
ucx=https://github.com/openucx/ucx.git

[patch_versions]
spdk=0001_3428322b812fe31cc3e1d0308a7f5bd4b06b9886.diff,0002_spdk_rwf_nowait.patch,0003_external_isal.patch
mercury=0001_dep_versions.patch,0002_ofi_counters.patch,0003_ofi_auth_key.patch
argobots=0001_411e5b344642ebc82190fd8b125db512e5b449d1.diff,0002_bb0c908abfac4bfe37852eee621930634183c6aa.diff
mercury=0001_dep_versions.patch,0002_ofi_counters.patch,0003_ofi_auth_key.patch
spdk=0001_3428322b812fe31cc3e1d0308a7f5bd4b06b9886.diff,0002_spdk_rwf_nowait.patch,0003_external_isal.patch
234 changes: 234 additions & 0 deletions utils/githooks/pre-commit.d/80-build.config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#!/usr/bin/env python3
#
# Copyright 2026 Google LLC
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
# Runs lint and auto-fix for utils/build.config for the DAOS project.

"""Git hook script to check and fix the format of utils/build.config."""

import os
import subprocess # nosec B404
import sys
import unittest


def print_githook_header(name):
"""Print the standard git hook header."""
print(f"{name + ':':<17} ", end='', flush=True)


def git_diff_cached_files(file_path):
"""Check if the file has staged changes."""
target = os.environ.get('TARGET')
if not target:
# If TARGET is not set, we can't easily determine what to diff against
# in the same way hook_base.sh does. For now, we'll return True to
# force a check if we're not sure, or try to find a base.
return True

cmd = ['git', 'diff', target, '--cached', '--name-only', '--diff-filter=d', '--', file_path]
result = subprocess.run(
cmd, capture_output=True, text=True, check=False,
shell=False) # nosec B603
return result.stdout.strip()
Comment on lines +17 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does it not work in the env to call the bash equivalents for these functions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I'm not sure which functions you are referring to here. Maybe commented on a different line than you wanted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean instead of writing print_githook_header can we not call the bash function for it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The short answer is the bash function is defined in hook_base.sh and not visible to python. And it's a one-liner. Maybe what we should do is create a python library to do what the bash scripts do and then we could invoke python from bash :-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you tried calling it from python? It does seem in my env at least that it works. I defined this bash script

#!/usr/bin/env bash

function _print_githook_header() {
    printf "Inside _print_githook_header\n"
}
export -f _print_githook_header

python3 ./my_python.py

And this python

#!/usr/bin/env python3

import subprocess

subprocess.run('_print_githook_header', shell=True)

And it seems to work

$ ./my_bash.sh 
Inside _print_githook_header

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, for git_diff_cached_files the env TARGET should be set because it was exported in hook_base.sh



def process_build_config(lines):
"""Parse, sort, and normalize the build config lines."""
global_lines = []
sections = []
current_section = None

for line in lines:
stripped = line.strip()
if stripped.startswith('[') and stripped.endswith(']'):
new_section = stripped[1:-1]
current_section = {'name': new_section, 'header': line, 'lines': []}
sections.append(current_section)
elif current_section is None:
global_lines.append(line)
else:
current_section['lines'].append(line)

def get_units(lines):
units = []
current_comments = []
for line in lines:
stripped = line.strip()
if not stripped:
continue
if stripped.startswith(('#', ';')):
current_comments.append(line)
elif '=' in stripped:
key, val = line.split('=', 1)
units.append({
'comments': current_comments,
'key': key.strip().lower(),
'val': val.strip()
})
current_comments = []
else:
current_comments.append(line)
if current_comments:
units.append({
'comments': current_comments,
'key': None,
'val': None
})
return units

def format_unit(unit):
res = []
res.extend(unit['comments'])
if unit['key'] is not None:
res.append(f"{unit['key']}={unit['val']}\n")
return res

def format_section(sec):
res = []
res.append(sec['header'])
units = get_units(sec['lines'])
key_units = sorted([u for u in units if u['key'] is not None],
key=lambda x: x['key'])
other_units = [u for u in units if u['key'] is None]
for u in key_units:
res.extend(format_unit(u))
for u in other_units:
res.extend(format_unit(u))
return res

new_content = []
global_filtered = [line for line in global_lines if line.strip()]
new_content.extend(global_filtered)

for i, sec in enumerate(sections):
if i > 0:
new_content.append("\n")
new_content.extend(format_section(sec))

return new_content


def run_tests():
"""Run unit tests for the functional bits."""
class TestBuildConfig(unittest.TestCase):
"""Unit tests for build config processing."""

def test_sorting(self):
"""Test that sections are sorted."""
lines = [
"[section]\n",
"b=2\n",
"a=1\n"
]
expected = [
"[section]\n",
"a=1\n",
"b=2\n"
]
self.assertEqual(process_build_config(lines), expected)

def test_casing(self):
"""Test that keys are lowercase."""
lines = [
"[section]\n",
"Key=Val\n"
]
expected = [
"[section]\n",
"key=Val\n"
]
self.assertEqual(process_build_config(lines), expected)

def test_comments_and_empty_lines(self):
"""Test that comments stay with following lines and empty lines are removed."""
lines = [
"[section]\n",
"b=2\n",
"\n",
"# comment\n",
"a=1\n"
]
expected = [
"[section]\n",
"# comment\n",
"a=1\n",
"b=2\n"
]
self.assertEqual(process_build_config(lines), expected)

def test_section_spacing(self):
"""Test that sections are separated by exactly one empty line."""
lines = [
"[sec1]\n",
"a=1\n",
"[sec2]\n",
"b=2\n"
]
expected = [
"[sec1]\n",
"a=1\n",
"\n",
"[sec2]\n",
"b=2\n"
]
self.assertEqual(process_build_config(lines), expected)

def test_no_empty_lines_at_ends(self):
"""Test that no empty lines are at start or end of file."""
lines = [
"\n",
"[sec1]\n",
"a=1\n",
"\n"
]
expected = [
"[sec1]\n",
"a=1\n"
]
self.assertEqual(process_build_config(lines), expected)

suite = unittest.TestLoader().loadTestsFromTestCase(TestBuildConfig)
result = unittest.TextTestRunner(verbosity=2).run(suite)
sys.exit(0 if result.wasSuccessful() else 1)


def main():
"""Main entry point for the git hook."""
if len(sys.argv) > 1 and sys.argv[1] == '--test':
run_tests()

print_githook_header("Build Config")

filename = "utils/build.config"

if not git_diff_cached_files(filename):
print(f"No changes to {filename}. Skipping")
sys.exit(0)

print(f"Checking and fixing {filename} for sorting and casing")

if not os.path.exists(filename):
sys.exit(0)

with open(filename, 'r', encoding='utf-8') as file_handle:
lines = file_handle.readlines()

new_content = process_build_config(lines)

if lines != new_content:
with open(filename, 'w', encoding='utf-8') as file_handle:
file_handle.writelines(new_content)
msg = (f" ERROR: {filename} was not correctly formatted. "
"Auto-fixes applied.")
print(msg, file=sys.stderr)
msg = (f" Please review with 'git diff {filename}' and "
"re-stage the changes.")
print(msg, file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions utils/utest.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# (C) Copyright 2023-2024 Intel Corporation.
# (C) Copyright 2026 Google LLC
# (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP.
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
Expand Down Expand Up @@ -221,3 +222,8 @@
- cmd: ["src/engine/tests/abt_stack", "--check-overflow", "--on-pool"]
env_vars:
ABT_STACK_OVERFLOW_CHECK: "mprotect_strict"
- name: Githooks
base: "DAOS_BASE"
memcheck: False
tests:
- cmd: ["utils/githooks/pre-commit.d/80-build.config.py", "--test"]
Loading