From 17a87cd858d9f01e24742eef0d2b555ad0d2e6e6 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 23 Mar 2026 12:45:49 +0100 Subject: [PATCH 1/3] Simplify BuildImage Signed-off-by: Robert Schmidt --- ci-scripts/cls_containerize.py | 105 +++++++++++---------------------- 1 file changed, 36 insertions(+), 69 deletions(-) diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py index c7831136e2..461c64d133 100644 --- a/ci-scripts/cls_containerize.py +++ b/ci-scripts/cls_containerize.py @@ -184,11 +184,6 @@ class Containerize(): self.services = '' self.deploymentTag = '' - self.cli = '' - self.cliBuildOptions = '' - self.dockerfileprefix = '' - self.host = '' - self.num_attempts = 1 self.flexricTag = '' @@ -203,18 +198,7 @@ class Containerize(): cmd = cls_cmd.getConnection(node) log_files = [] - # Checking the hostname to get adapted on cli and dockerfileprefixes - cmd.run('hostnamectl') - result = re.search('Ubuntu|Red Hat', cmd.getBefore()) - self.host = result.group(0) - if self.host == 'Ubuntu': - self.cli = 'docker' - self.dockerfileprefix = '.ubuntu' - self.cliBuildOptions = '' - elif self.host == 'Red Hat': - self.cli = 'sudo podman' - self.dockerfileprefix = '.rhel9' - self.cliBuildOptions = '--disable-compression' + dockerfileprefix = '.ubuntu' # we always build the ran-build image with all targets # Creating a tupple with the imageName, the DockerFile prefix pattern, targetName and sanitized option @@ -232,23 +216,20 @@ class Containerize(): imageNames.append(('oai-nr-cuup', 'nr-cuup', 'oai-nr-cuup', '')) imageNames.append(('oai-lte-ue', 'lteUE', 'oai-lte-ue', '')) imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue', '')) - if self.host == 'Red Hat': - imageNames.append(('oai-physim', 'phySim', 'oai-physim', '')) - if self.host == 'Ubuntu': - imageNames.append(('oai-lte-ru', 'lteRU', 'oai-lte-ru', '')) - # Building again the 5G images with Address Sanitizer - imageNames.append(('ran-build', 'build', 'ran-build-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('oai-enb', 'eNB', 'oai-enb-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('oai-gnb', 'gNB', 'oai-gnb-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('oai-lte-ue', 'lteUE', 'oai-lte-ue-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('oai-nr-cuup', 'nr-cuup', 'oai-nr-cuup-asan', '--build-arg "BUILD_OPTION=--sanitize"')) - imageNames.append(('ran-build-fhi72', 'build.fhi72', 'ran-build-fhi72', '')) - imageNames.append(('oai-gnb', 'gNB.fhi72', 'oai-gnb-fhi72', '')) - imageNames.append(('oai-nr-oru', 'nrORU.fhi72', 'oai-nr-oru', '')) + imageNames.append(('oai-lte-ru', 'lteRU', 'oai-lte-ru', '')) + # Building again the 5G images with Address Sanitizer + imageNames.append(('ran-build', 'build', 'ran-build-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('oai-enb', 'eNB', 'oai-enb-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('oai-gnb', 'gNB', 'oai-gnb-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('oai-lte-ue', 'lteUE', 'oai-lte-ue-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('oai-nr-cuup', 'nr-cuup', 'oai-nr-cuup-asan', '--build-arg "BUILD_OPTION=--sanitize"')) + imageNames.append(('ran-build-fhi72', 'build.fhi72', 'ran-build-fhi72', '')) + imageNames.append(('oai-gnb', 'gNB.fhi72', 'oai-gnb-fhi72', '')) + imageNames.append(('oai-nr-oru', 'nrORU.fhi72', 'oai-nr-oru', '')) result = re.search('build_cross_arm64', self.imageKind) if result is not None: - self.dockerfileprefix = '.ubuntu.cross-arm64' + dockerfileprefix = '.ubuntu.cross-arm64' result = re.search('native_armv9', self.imageKind) if result is not None: imageNames.append(('oai-gnb', 'gNB', 'oai-gnb', '')) @@ -263,10 +244,6 @@ class Containerize(): imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue', '')) cmd.cd(lSourcePath) - # if asterix, copy the entitlement and subscription manager configurations - if self.host == 'Red Hat': - cmd.run('mkdir -p ./etc-pki-entitlement') - cmd.run('cp /etc/pki/entitlement/*.pem ./etc-pki-entitlement/') baseImage = 'ran-base' baseTag = 'develop' @@ -275,7 +252,7 @@ class Containerize(): if (self.ranAllowMerge): imageTag = 'ci-temp' if self.ranTargetBranch == 'develop': - cmd.run(f'git diff HEAD..origin/develop -- cmake_targets/build_oai cmake_targets/tools/build_helper docker/Dockerfile.base{self.dockerfileprefix} | grep --colour=never -i INDEX') + cmd.run(f'git diff HEAD..origin/develop -- cmake_targets/build_oai cmake_targets/tools/build_helper docker/Dockerfile.base{dockerfileprefix} | grep --colour=never -i INDEX') result = re.search('index', cmd.getBefore()) if result is not None: forceBaseImageBuild = True @@ -289,31 +266,30 @@ class Containerize(): forceBaseImageBuild = True # Let's remove any previous run artifacts if still there - cmd.run(f"{self.cli} image prune --force") + cmd.run(f"docker image prune --force") for image,pattern,name,option in imageNames: - cmd.run(f"{self.cli} image rm {name}:{imageTag}", reportNonZero=False) + cmd.run(f"docker image rm {name}:{imageTag}", reportNonZero=False) cmd.run(f'docker login -u oaicicd -p oaicicd {DEFAULT_REGISTRY}') ubuntuImage = "ubuntu:noble" # Build the base image only on Push Events (not on Merge Requests) # On when the base image docker file is being modified. if forceBaseImageBuild: - cmd.run(f"{self.cli} image rm {baseImage}:{baseTag}") + cmd.run(f"docker image rm {baseImage}:{baseTag}") logfile = f'{lSourcePath}/cmake_targets/log/ran-base.docker.log' - if self.host == 'Ubuntu': - option = f" --build-arg UBUNTU_IMAGE={DEFAULT_REGISTRY}/{ubuntuImage}" - cmd.run(f"{self.cli} build {self.cliBuildOptions} --target {baseImage} --tag {baseImage}:{baseTag} --file docker/Dockerfile.base{self.dockerfileprefix} {option} . &> {logfile}", timeout=1600) + option = f" --build-arg UBUNTU_IMAGE={DEFAULT_REGISTRY}/{ubuntuImage}" + cmd.run(f"docker build --target {baseImage} --tag {baseImage}:{baseTag} --file docker/Dockerfile.base{dockerfileprefix} {option} . &> {logfile}", timeout=1600) t = ("ran-base", archiveArtifact(cmd, ctx, logfile)) log_files.append(t) # First verify if the base image was properly created. - ret = cmd.run(f"{self.cli} image inspect --format=\'Size = {{{{.Size}}}} bytes\' {baseImage}:{baseTag}") + ret = cmd.run(f"docker image inspect --format=\'Size = {{{{.Size}}}} bytes\' {baseImage}:{baseTag}") allImagesSize = {} if ret.returncode != 0: logging.error('\u001B[1m Could not build properly ran-base\u001B[0m') # Recover the name of the failed container? - cmd.run(f"{self.cli} ps --quiet --filter \"status=exited\" -n1 | xargs --no-run-if-empty {self.cli} rm -f") - cmd.run(f"{self.cli} image prune --force") + cmd.run(f"docker ps --quiet --filter \"status=exited\" -n1 | xargs --no-run-if-empty docker rm -f") + cmd.run(f"docker image prune --force") cmd.close() logging.error('\u001B[1m Building OAI Images Failed\u001B[0m') HTML.CreateHtmlTestRow(self.imageKind, 'KO', CONST.ALL_PROCESSES_OK) @@ -333,32 +309,31 @@ class Containerize(): for image,pattern,name,option in imageNames: # the archived Dockerfiles have "ran-base:latest" as base image # we need to update them with proper tag - cmd.run(f'git checkout -- docker/Dockerfile.{pattern}{self.dockerfileprefix}') - cmd.run(f'sed -i -e "s#{baseImage}:latest#{baseImage}:{baseTag}#" docker/Dockerfile.{pattern}{self.dockerfileprefix}') + cmd.run(f'git checkout -- docker/Dockerfile.{pattern}{dockerfileprefix}') + cmd.run(f'sed -i -e "s#{baseImage}:latest#{baseImage}:{baseTag}#" docker/Dockerfile.{pattern}{dockerfileprefix}') # target images should use the proper ran-build image if image != 'ran-build' and "-asan" in name: - cmd.run(f'sed -i -e "s#ran-build:latest#ran-build-asan:{imageTag}#" docker/Dockerfile.{pattern}{self.dockerfileprefix}') + cmd.run(f'sed -i -e "s#ran-build:latest#ran-build-asan:{imageTag}#" docker/Dockerfile.{pattern}{dockerfileprefix}') elif "fhi72" in name or name == "oai-nr-oru": - cmd.run(f'sed -i -e "s#ran-build-fhi72:latest#ran-build-fhi72:{imageTag}#" docker/Dockerfile.{pattern}{self.dockerfileprefix}') + cmd.run(f'sed -i -e "s#ran-build-fhi72:latest#ran-build-fhi72:{imageTag}#" docker/Dockerfile.{pattern}{dockerfileprefix}') elif image != 'ran-build': - cmd.run(f'sed -i -e "s#ran-build:latest#ran-build:{imageTag}#" docker/Dockerfile.{pattern}{self.dockerfileprefix}') + cmd.run(f'sed -i -e "s#ran-build:latest#ran-build:{imageTag}#" docker/Dockerfile.{pattern}{dockerfileprefix}') if image == 'oai-gnb-aerial': cmd.run('cp -f /opt/nvidia-ipc/nvipc_src.2026.01.07.tar.gz .') logfile = f'{lSourcePath}/cmake_targets/log/{name}.docker.log' - if self.host == 'Ubuntu': - option = option + f" --build-arg UBUNTU_IMAGE={DEFAULT_REGISTRY}/{ubuntuImage}" - ret = cmd.run(f'{self.cli} build {self.cliBuildOptions} --target {image} --tag {name}:{imageTag} --file docker/Dockerfile.{pattern}{self.dockerfileprefix} {option} . > {logfile} 2>&1', timeout=1200) + option = option + f" --build-arg UBUNTU_IMAGE={DEFAULT_REGISTRY}/{ubuntuImage}" + ret = cmd.run(f'docker build --target {image} --tag {name}:{imageTag} --file docker/Dockerfile.{pattern}{dockerfileprefix} {option} . > {logfile} 2>&1', timeout=1200) t = (name, archiveArtifact(cmd, ctx, logfile)) log_files.append(t) if image == 'oai-gnb-aerial': cmd.run('rm -f nvipc_src.2026.01.07.tar.gz') # check the status of the build - ret = cmd.run(f"{self.cli} image inspect --format=\'Size = {{{{.Size}}}} bytes\' {name}:{imageTag}") + ret = cmd.run(f"docker image inspect --format=\'Size = {{{{.Size}}}} bytes\' {name}:{imageTag}") if ret.returncode != 0: logging.error('\u001B[1m Could not build properly ' + name + '\u001B[0m') status = False # Here we should check if the last container corresponds to a failed command and destroy it - cmd.run(f"{self.cli} ps --quiet --filter \"status=exited\" -n1 | xargs --no-run-if-empty {self.cli} rm -f") + cmd.run(f"docker ps --quiet --filter \"status=exited\" -n1 | xargs --no-run-if-empty docker rm -f") allImagesSize[name] = 'N/A -- Build Failed' break else: @@ -372,16 +347,16 @@ class Containerize(): logging.debug(f'{name} size is unknown') allImagesSize[name] = 'unknown' # Now pruning dangling images in between target builds - cmd.run(f"{self.cli} image prune --force") + cmd.run(f"docker image prune --force") cmd.run(f'docker logout {DEFAULT_REGISTRY}') # Remove all intermediate build images and clean up - cmd.run(f"{self.cli} image rm ran-build:{imageTag} ran-build-asan:{imageTag} ran-build-fhi72:{imageTag} || true") - cmd.run(f"{self.cli} volume prune --force") + cmd.run(f"docker image rm ran-build:{imageTag} ran-build-asan:{imageTag} ran-build-fhi72:{imageTag} || true") + cmd.run(f"docker volume prune --force") # Remove some cached artifacts to prevent out of diskspace problem logging.debug(cmd.run("df -h").stdout) logging.debug(cmd.run("docker system df").stdout) - cmd.run(f"{self.cli} buildx prune --filter until=1h --force") + cmd.run(f"docker buildx prune --filter until=1h --force") logging.debug(cmd.run("df -h").stdout) logging.debug(cmd.run("docker system df").stdout) @@ -489,20 +464,12 @@ class Containerize(): cmd = cls_cmd.getConnection(node) cmd.cd(lSourcePath) - ret = cmd.run('hostnamectl') - result = re.search('Ubuntu', ret.stdout) - host = result.group(0) - if host != 'Ubuntu': - cmd.close() - raise Exception("Can build unit tests only on Ubuntu server") - logging.debug('running on Ubuntu as expected') - # check that ran-base image exists as we expect it baseImage = 'ran-base' baseTag = 'develop' if self.ranAllowMerge: if self.ranTargetBranch == 'develop': - cmd.run(f'git diff HEAD..origin/develop -- cmake_targets/build_oai cmake_targets/tools/build_helper docker/Dockerfile.base{self.dockerfileprefix} | grep --colour=never -i INDEX') + cmd.run(f'git diff HEAD..origin/develop -- cmake_targets/build_oai cmake_targets/tools/build_helper docker/Dockerfile.base.ubuntu | grep --colour=never -i INDEX') result = re.search('index', cmd.getBefore()) if result is not None: baseTag = 'ci-temp' From 1c512f3dc56d83025dd0ffaae17120739acf6d29 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 3 Mar 2026 18:09:20 +0100 Subject: [PATCH 2/3] Remove cppcheck from CI, add to tools/ We don't enforce cppcheck through the CI, although it's there since years. It runs on Ubuntu 18/20, so it's old. For folks, it's likely not discoverable on how to run it locally. Let's make a fresh start. This removes cppcheck from all CI-related code. Instead, it adds it under tools/cppcheck/, including documentation on how to run it locally, bare-matel or in docker. Signed-off-by: Robert Schmidt --- ci-scripts/Jenkinsfile-GitLab-Container | 22 ---- ci-scripts/cls_oai_html.py | 84 ------------ ci-scripts/cls_static_code_analysis.py | 120 ------------------ ci-scripts/docker/Dockerfile.cppcheck.xenial | 41 ------ ci-scripts/main.py | 3 - ci-scripts/xml_class_list.yml | 1 - ci-scripts/xml_files/cppcheck.xml | 20 --- doc/README.md | 1 + doc/TESTBenches.md | 4 - tools/cppcheck/README.md | 28 ++++ tools/cppcheck/docker-compose.yaml | 14 ++ tools/cppcheck/run-cppcheck.sh | 11 ++ .../cppcheck/suppressions.list | 0 13 files changed, 54 insertions(+), 295 deletions(-) delete mode 100644 ci-scripts/docker/Dockerfile.cppcheck.xenial delete mode 100644 ci-scripts/xml_files/cppcheck.xml create mode 100644 tools/cppcheck/README.md create mode 100644 tools/cppcheck/docker-compose.yaml create mode 100755 tools/cppcheck/run-cppcheck.sh rename ci-scripts/cppcheck_suppressions.list => tools/cppcheck/suppressions.list (100%) diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index d75b1888d4..721c89b77f 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -219,28 +219,6 @@ pipeline { } } } - stage ("cppcheck") { - steps { - script { - triggerSlaveJob ('RAN-cppcheck', 'cppcheck') - } - } - post { - always { - script { - // Using a unique variable name for each test stage to avoid overwriting on a global variable - // due to parallel-time concurrency - cppcheckStatus = finalizeSlaveJob('RAN-cppcheck') - } - } - failure { - script { - currentBuild.result = 'FAILURE' - failingStages += cppcheckStatus - } - } - } - } stage ("ARM-Cross-Compile") { steps { script { diff --git a/ci-scripts/cls_oai_html.py b/ci-scripts/cls_oai_html.py index 2f59ae1852..66d4b804ae 100644 --- a/ci-scripts/cls_oai_html.py +++ b/ci-scripts/cls_oai_html.py @@ -379,90 +379,6 @@ class HTMLManagement(): self.htmlFile.write(' \n') self.htmlFile.close() - def CreateHtmlTestRowCppCheckResults(self, CCR): - if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): - return - self.htmlFile = open('test_results.html', 'a') - vId = 0 - for version in CCR.versions: - self.htmlFile.write(' \n') - self.htmlFile.write(' Results for cppcheck v ' + version + ' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' NB ERRORS\n') - if CCR.nbErrors[vId] == 0: - myColor = 'lightgreen' - elif CCR.nbErrors[vId] < 20: - myColor = 'orange' - else: - myColor = 'lightcoral' - self.htmlFile.write(' ' + str(CCR.nbErrors[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' NB WARNINGS\n') - if CCR.nbWarnings[vId] == 0: - myColor = 'lightgreen' - elif CCR.nbWarnings[vId] < 20: - myColor = 'orange' - else: - myColor = 'lightcoral' - self.htmlFile.write(' ' + str(CCR.nbWarnings[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' ----------------- \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Memory leak\n') - self.htmlFile.write(' ' + str(CCR.nbMemLeaks[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Possible null pointer deference\n') - self.htmlFile.write(' ' + str(CCR.nbNullPtrs[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Uninitialized variable\n') - self.htmlFile.write(' ' + str(CCR.nbUninitVars[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Undefined behaviour shifting\n') - self.htmlFile.write(' ' + str(CCR.nbTooManyBitsShift[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Signed integer overflow\n') - self.htmlFile.write(' ' + str(CCR.nbIntegerOverflow[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Printf formatting issues\n') - self.htmlFile.write(' ' + str(CCR.nbInvalidPrintf[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Modulo result is predetermined\n') - self.htmlFile.write(' ' + str(CCR.nbModuloAlways[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Opposite Condition -> dead code\n') - self.htmlFile.write(' ' + str(CCR.nbOppoInnerCondition[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Wrong Scanf Nb Args\n') - self.htmlFile.write(' ' + str(CCR.nbWrongScanfArg[vId]) + '\n') - self.htmlFile.write(' \n') - vId += 1 - def CreateHtmlTestRowPhySimTestResult(self, testSummary, testResult): if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): return diff --git a/ci-scripts/cls_static_code_analysis.py b/ci-scripts/cls_static_code_analysis.py index 603df581e3..4e388847c7 100644 --- a/ci-scripts/cls_static_code_analysis.py +++ b/ci-scripts/cls_static_code_analysis.py @@ -27,28 +27,6 @@ import constants as CONST import cls_cmd from cls_ci_helper import archiveArtifact -#----------------------------------------------------------- -# Class Declaration -#----------------------------------------------------------- -class CppCheckResults(): - - def __init__(self): - - self.variants = ['bionic', 'focal'] - self.versions = ['',''] - self.nbErrors = [0,0] - self.nbWarnings = [0,0] - self.nbNullPtrs = [0,0] - self.nbMemLeaks = [0,0] - self.nbUninitVars = [0,0] - self.nbInvalidPrintf = [0,0] - self.nbModuloAlways = [0,0] - self.nbTooManyBitsShift = [0,0] - self.nbIntegerOverflow = [0,0] - self.nbWrongScanfArg = [0,0] - self.nbPtrAddNotNull = [0,0] - self.nbOppoInnerCondition = [0,0] - class StaticCodeAnalysis(): def __init__(self): @@ -60,104 +38,6 @@ class StaticCodeAnalysis(): self.ranTargetBranch = '' self.eNBSourceCodePath = '' - def CppCheckAnalysis(self, ctx, node, HTML): - if self.ranRepository == '' or self.ranBranch == '' or self.ranCommitID == '': - HELP.GenericHelp(CONST.Version) - sys.exit('Insufficient Parameter') - lSourcePath = self.eNBSourceCodePath - - if not lSourcePath or not node: - raise ValueError(f"{lSourcePath=} {node=}") - logging.debug('Building on server: ' + node) - cmd = cls_cmd.getConnection(node) - # on RedHat/CentOS .git extension is mandatory - result = re.search(r'([a-zA-Z0-9\:\-\.\/])+\.git', self.ranRepository) - if result is not None: - full_ran_repo_name = self.ranRepository.replace('git/', 'git') - else: - full_ran_repo_name = self.ranRepository + '.git' - - cmd.cd(lSourcePath) - logDir = f'{lSourcePath}/cmake_targets/log' - cmd.run(f'mkdir -p {logDir}') - cmd.run('docker image rm oai-cppcheck:bionic oai-cppcheck:focal') - cmd.run(f'sed -e "s@xenial@bionic@" {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.xenial > {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.bionic') - cmd.run(f'docker build --tag oai-cppcheck:bionic --file {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.bionic . > {logDir}/cppcheck-bionic.txt 2>&1') - cmd.run(f'sed -e "s@xenial@focal@" {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.xenial > {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.focal') - cmd.run(f'docker build --tag oai-cppcheck:focal --file {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.focal . > {logDir}/cppcheck-focal.txt 2>&1') - cmd.run('docker image rm oai-cppcheck:bionic oai-cppcheck:focal') - - bionic = archiveArtifact(cmd, ctx, f'{logDir}/cppcheck-bionic.txt') - focal = archiveArtifact(cmd, ctx, f'{logDir}/cppcheck-focal.txt') - cmd.close() - - CCR = CppCheckResults() - CCR_ref = CppCheckResults() - vId = 0 - for variant in CCR.variants: - filename = ctx.baseFilename() + '-cppcheck-'+ variant + '.txt' - logging.info(f"will check file '{filename}'") - if not os.path.isfile(filename): - raise FileNotFoundError(f"{filename} is not a file") - else: - xmlStart = False - with open(filename, 'r') as logfile: - for line in logfile: - ret = re.search(r'cppcheck version="(?P[0-9\.]+)"', str(line)) - if ret is not None: - CCR.versions[vId] = ret.group('version') - if re.search('RUN cat cmake_targets/log/cppcheck.xml', str(line)) is not None: - xmlStart = True - if xmlStart: - if re.search('severity="error"', str(line)) is not None: - CCR.nbErrors[vId] += 1 - if re.search('severity="warning"', str(line)) is not None: - CCR.nbWarnings[vId] += 1 - if re.search('id="memleak"', str(line)) is not None: - CCR.nbMemLeaks[vId] += 1 - if re.search('id="nullPointer"', str(line)) is not None: - CCR.nbNullPtrs[vId] += 1 - if re.search('id="uninitvar"', str(line)) is not None: - CCR.nbUninitVars[vId] += 1 - if re.search('id="invalidPrintfArgType_sint"|id="invalidPrintfArgType_uint"', str(line)) is not None: - CCR.nbInvalidPrintf[vId] += 1 - if re.search('id="moduloAlwaysTrueFalse"', str(line)) is not None: - CCR.nbModuloAlways[vId] += 1 - if re.search('id="shiftTooManyBitsSigned"', str(line)) is not None: - CCR.nbTooManyBitsShift[vId] += 1 - if re.search('id="integerOverflow"', str(line)) is not None: - CCR.nbIntegerOverflow[vId] += 1 - if re.search('id="wrongPrintfScanfArgNum"|id="invalidScanfArgType_int"', str(line)) is not None: - CCR.nbWrongScanfArg[vId] += 1 - if re.search('id="pointerAdditionResultNotNull"', str(line)) is not None: - CCR.nbPtrAddNotNull[vId] += 1 - if re.search('id="oppositeInnerCondition"', str(line)) is not None: - CCR.nbOppoInnerCondition[vId] += 1 - vMsg = '' - vMsg += '======== Variant ' + variant + ' - ' + CCR.versions[vId] + ' ========\n' - vMsg += ' ' + str(CCR.nbErrors[vId]) + ' errors\n' - vMsg += ' ' + str(CCR.nbWarnings[vId]) + ' warnings\n' - vMsg += ' -- Details --\n' - vMsg += ' Memory leak: ' + str(CCR.nbMemLeaks[vId]) + '\n' - vMsg += ' Possible null pointer deference: ' + str(CCR.nbNullPtrs[vId]) + '\n' - vMsg += ' Uninitialized variable: ' + str(CCR.nbUninitVars[vId]) + '\n' - vMsg += ' Undefined behaviour shifting: ' + str(CCR.nbTooManyBitsShift[vId]) + '\n' - vMsg += ' Signed integer overflow: ' + str(CCR.nbIntegerOverflow[vId]) + '\n' - vMsg += '\n' - vMsg += ' Printf formatting issue: ' + str(CCR.nbInvalidPrintf[vId]) + '\n' - vMsg += ' Modulo result is predetermined: ' + str(CCR.nbModuloAlways[vId]) + '\n' - vMsg += ' Opposite Condition -> dead code: ' + str(CCR.nbOppoInnerCondition[vId]) + '\n' - vMsg += ' Wrong Scanf Nb Args: ' + str(CCR.nbWrongScanfArg[vId]) + '\n' - for vLine in vMsg.split('\n'): - logging.debug(vLine) - vId += 1 - - HTML.CreateHtmlTestRow('N/A', 'OK', CONST.ALL_PROCESSES_OK) - HTML.CreateHtmlTestRowCppCheckResults(CCR) - logging.info('\u001B[1m Static Code Analysis Pass\u001B[0m') - - return True - def LicenceAndFormattingCheck(self, ctx, node, HTML): # Workspace is no longer recreated from scratch. # It implies that this method shall be called last within a build pipeline diff --git a/ci-scripts/docker/Dockerfile.cppcheck.xenial b/ci-scripts/docker/Dockerfile.cppcheck.xenial deleted file mode 100644 index fcb7f814ae..0000000000 --- a/ci-scripts/docker/Dockerfile.cppcheck.xenial +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: LicenseRef-CSSL-1.0 - -#--------------------------------------------------------------------- -# -# Dockerfile for the Open-Air-Interface BUILD service -# Valid for Ubuntu 22.04 -# -#--------------------------------------------------------------------- -FROM ubuntu:xenial AS oai-cppcheck - - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get upgrade --yes && \ - apt-get install --yes \ - build-essential \ - vim \ - cppcheck - -WORKDIR /oai-ran -COPY . . - -WORKDIR /oai-ran/common/utils/T -RUN make - -WORKDIR /oai-ran -RUN mkdir -p cmake_targets/log && \ - cppcheck --enable=warning --force --xml --xml-version=2 \ - --inline-suppr \ - -i openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_decoder.c \ - --suppressions-list=ci-scripts/cppcheck_suppressions.list \ - -I common/utils \ - -I openair3/NAS/COMMON/UTIL \ - -j`nproc` . 2> cmake_targets/log/cppcheck.xml 1> cmake_targets/log/cppcheck_build.txt - -RUN grep -E -c 'severity="error' cmake_targets/log/cppcheck.xml - -RUN grep -E -c 'severity="warning' cmake_targets/log/cppcheck.xml - -RUN cat cmake_targets/log/cppcheck.xml diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 27cf36bcf8..906f73f433 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -198,9 +198,6 @@ def ExecuteActionWithParam(action, ctx, node): elif action == 'LicenceAndFormattingCheck': success = SCA.LicenceAndFormattingCheck(ctx, node, HTML) - elif action == 'Cppcheck_Analysis': - success = SCA.CppCheckAnalysis(ctx, node, HTML) - elif action == 'Push_Local_Registry': tag_prefix = test.findtext('tag_prefix') or "" success = CONTAINERS.Push_Image_to_Local_Registry(node, HTML, tag_prefix) diff --git a/ci-scripts/xml_class_list.yml b/ci-scripts/xml_class_list.yml index 52b44e0e96..d762c4b316 100755 --- a/ci-scripts/xml_class_list.yml +++ b/ci-scripts/xml_class_list.yml @@ -23,7 +23,6 @@ - Deploy_Object - Stop_Object - Undeploy_Object - - Cppcheck_Analysis - Deploy_Run_OC_PhySim - Build_Deploy_PhySim - LicenceAndFormattingCheck diff --git a/ci-scripts/xml_files/cppcheck.xml b/ci-scripts/xml_files/cppcheck.xml deleted file mode 100644 index 061c39b5b8..0000000000 --- a/ci-scripts/xml_files/cppcheck.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - build-tab - CPPCHECK Analysis - wrench - - - Create_Workspace - Create new Workspace - carabe - - - - Cppcheck_Analysis - Static Code Analysis with cppcheck - carabe - - - diff --git a/doc/README.md b/doc/README.md index 37fe2a980d..d1dfc2dc4d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -135,6 +135,7 @@ The other SDRs (AW2S, LimeSDR, ...) have no READMEs. - [clang-format.md](./clang-format.md): how to format the code. See also the next entry for an error detection tool. - [formatting](../tools/formatting/README.md) is a clang-format error detection tool +- [cppcheck](../tools/cppcheck/README.md) for static code analysis - [sanitizers.md](./dev_tools/sanitizers.md): how to run with ASan/UBSan/MemSAN/TSan - [iwyu](../tools/iwyu/README.md) is a tool to detect `#include` errors - [docker-dev-env](../tools/docker-dev-env/README.md) is a ubuntu24 docker development environment diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index d348ffe355..361d774215 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -96,10 +96,6 @@ information on how the images are built. - orion: Cross-compilation from Intel to ARM - base image from `Dockerfile.base.ubuntu.cross-arm64` - build image from `Dockerfile.build.ubuntu.cross-arm64` (no target images) -- [RAN-cppcheck](https://jenkins-oai.eurecom.fr/job/RAN-cppcheck/) - ~BUILD-ONLY ~4G-LTE ~5G-NR ~nrUE - - bellatrix - - performs static code analysis, currently not actively enforced - [RAN-RHEL8-Cluster-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-RHEL8-Cluster-Image-Builder/) ~BUILD-ONLY ~4G-LTE ~5G-NR ~nrUE - cluster (`Asterix-OC-oaicicd-session` resource): RHEL image build using the OpenShift Cluster (using gcc/clang) diff --git a/tools/cppcheck/README.md b/tools/cppcheck/README.md new file mode 100644 index 0000000000..414f35a68a --- /dev/null +++ b/tools/cppcheck/README.md @@ -0,0 +1,28 @@ + + +# Static code analysis + +How to run static code analysis tool(s). + +[[_TOC_]] + +## cppcheck Overview + +This can run a (dockerized) cppcheck static code analysis. The docker-compose +file represents the status as run as in the CI until it's removal (because it +was not enforced). It is now here so that people could use it to find bugs (and +use the tool more easily). + +## cppcheck Usage + +From the openairinterface5g root, simply run + + ./tools/cppcheck/run-cppcheck.sh + +This will run cppcheck with options listed in this file, assuming you have it +installed. Alternatively, you can run the CI version as of Ubuntu 20 with + + cd tools/cppcheck && docker compose up --no-log-prefix + +You can upgrade to a newer version by modifying the `FROM` line in the +docker-compose file. diff --git a/tools/cppcheck/docker-compose.yaml b/tools/cppcheck/docker-compose.yaml new file mode 100644 index 0000000000..8b8c5878d0 --- /dev/null +++ b/tools/cppcheck/docker-compose.yaml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MIT + +services: + cppcheck: + image: cppcheck + build: + dockerfile_inline: | + FROM ubuntu:focal + RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y cppcheck + volumes: + - ../../:/code/ + command: + bash -c "cd /code/ && ./tools/cppcheck/run-cppcheck.sh" + diff --git a/tools/cppcheck/run-cppcheck.sh b/tools/cppcheck/run-cppcheck.sh new file mode 100755 index 0000000000..5a5df0c752 --- /dev/null +++ b/tools/cppcheck/run-cppcheck.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT + +cppcheck --enable=warning --force \ + --inline-suppr \ + --quiet \ + -i openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_decoder.c \ + --suppressions-list=tools/cppcheck/suppressions.list \ + -I common/utils \ + -I openair3/NAS/COMMON/UTIL \ + -j`nproc` . diff --git a/ci-scripts/cppcheck_suppressions.list b/tools/cppcheck/suppressions.list similarity index 100% rename from ci-scripts/cppcheck_suppressions.list rename to tools/cppcheck/suppressions.list From ff97f0dea611dc476a523b0edfef449e7eb7ab88 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Apr 2026 00:28:50 +0200 Subject: [PATCH 3/3] LicenceAndFormattingCheck: remove global variable, pass directly Signed-off-by: Robert Schmidt --- ci-scripts/args_parse.py | 8 +------ ci-scripts/cls_static_code_analysis.py | 30 +++++++++----------------- ci-scripts/main.py | 8 +++---- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/ci-scripts/args_parse.py b/ci-scripts/args_parse.py index 6206c67d69..16e23113ab 100644 --- a/ci-scripts/args_parse.py +++ b/ci-scripts/args_parse.py @@ -22,7 +22,7 @@ import constants as CONST #----------------------------------------------------------- -def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): +def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,CLUSTER): force_local = False date_fmt = None @@ -53,7 +53,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): RAN.ranRepository=matchReg.group(1) HTML.ranRepository=matchReg.group(1) CONTAINERS.ranRepository=matchReg.group(1) - SCA.ranRepository=matchReg.group(1) CLUSTER.ranRepository=matchReg.group(1) elif re.match(r'^\-\-eNB_AllowMerge=(.+)$|^\-\-ranAllowMerge=(.+)$', myArgv, re.IGNORECASE): if re.match(r'^\-\-eNB_AllowMerge=(.+)$', myArgv, re.IGNORECASE): @@ -66,7 +65,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): RAN.ranAllowMerge=True HTML.ranAllowMerge=True CONTAINERS.ranAllowMerge=True - SCA.ranAllowMerge=True CLUSTER.ranAllowMerge=True elif re.match(r'^\-\-eNBBranch=(.+)$|^\-\-ranBranch=(.+)$', myArgv, re.IGNORECASE): if re.match(r'^\-\-eNBBranch=(.+)$', myArgv, re.IGNORECASE): @@ -77,7 +75,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): RAN.ranBranch=matchReg.group(1) HTML.ranBranch=matchReg.group(1) CONTAINERS.ranBranch=matchReg.group(1) - SCA.ranBranch=matchReg.group(1) CLUSTER.ranBranch=matchReg.group(1) elif re.match(r'^\-\-eNBCommitID=(.*)$|^\-\-ranCommitID=(.*)$', myArgv, re.IGNORECASE): if re.match(r'^\-\-eNBCommitID=(.*)$', myArgv, re.IGNORECASE): @@ -88,7 +85,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): RAN.ranCommitID=matchReg.group(1) HTML.ranCommitID=matchReg.group(1) CONTAINERS.ranCommitID=matchReg.group(1) - SCA.ranCommitID=matchReg.group(1) CLUSTER.ranCommitID=matchReg.group(1) elif re.match(r'^\-\-eNBTargetBranch=(.*)$|^\-\-ranTargetBranch=(.*)$', myArgv, re.IGNORECASE): if re.match(r'^\-\-eNBTargetBranch=(.*)$', myArgv, re.IGNORECASE): @@ -99,7 +95,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): RAN.ranTargetBranch=matchReg.group(1) HTML.ranTargetBranch=matchReg.group(1) CONTAINERS.ranTargetBranch=matchReg.group(1) - SCA.ranTargetBranch=matchReg.group(1) CLUSTER.ranTargetBranch=matchReg.group(1) elif re.match(r'^\-\-eNBIPAddress=(.+)$|^\-\-eNB[1-2]IPAddress=(.+)$', myArgv, re.IGNORECASE): print("parameters --eNB*IPAddress ignored") @@ -112,7 +107,6 @@ def ArgsParse(argvs,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER): matchReg = re.match(r'^\-\-eNBSourceCodePath=(.+)$', myArgv, re.IGNORECASE) RAN.eNBSourceCodePath=matchReg.group(1) CONTAINERS.eNBSourceCodePath=matchReg.group(1) - SCA.eNBSourceCodePath=matchReg.group(1) CLUSTER.eNBSourceCodePath=matchReg.group(1) elif re.match(r'^\-\-eNB1SourceCodePath=(.+)$', myArgv, re.IGNORECASE): print("parameter --eNB1SourceCodePath ignored") diff --git a/ci-scripts/cls_static_code_analysis.py b/ci-scripts/cls_static_code_analysis.py index 4e388847c7..3b4ee10f9b 100644 --- a/ci-scripts/cls_static_code_analysis.py +++ b/ci-scripts/cls_static_code_analysis.py @@ -29,38 +29,28 @@ from cls_ci_helper import archiveArtifact class StaticCodeAnalysis(): - def __init__(self): - - self.ranRepository = '' - self.ranBranch = '' - self.ranAllowMerge = False - self.ranCommitID = '' - self.ranTargetBranch = '' - self.eNBSourceCodePath = '' - - def LicenceAndFormattingCheck(self, ctx, node, HTML): + def LicenceAndFormattingCheck(ctx, node, HTML, d, branch, allowMerge, targetBranch): # Workspace is no longer recreated from scratch. # It implies that this method shall be called last within a build pipeline # where workspace is already created - lSourcePath = self.eNBSourceCodePath - if not node or not lSourcePath: - raise ValueError(f"{lSourcePath=} {node=}") + if not node or not d: + raise ValueError(f"{d=} {node=}") logging.debug('Building on server: ' + node) cmd = cls_cmd.getConnection(node) check_options = '' - if self.ranAllowMerge: - check_options = f'--build-arg MERGE_REQUEST=true --build-arg SRC_BRANCH={self.ranBranch}' - if self.ranTargetBranch == '': - if self.ranBranch != 'develop' and self.ranBranch != 'origin/develop': + if allowMerge: + check_options = f'--build-arg MERGE_REQUEST=true --build-arg SRC_BRANCH={branch}' + if targetBranch == '': + if branch != 'develop' and branch != 'origin/develop': check_options += ' --build-arg TARGET_BRANCH=develop' else: - check_options += f' --build-arg TARGET_BRANCH={self.ranTargetBranch}' + check_options += f' --build-arg TARGET_BRANCH={targetBranch}' - logDir = f'{lSourcePath}/cmake_targets/log/' + logDir = f'{d}/cmake_targets/log/' cmd.run(f'mkdir -p {logDir}') cmd.run('docker image rm oai-formatting-check:latest') - cmd.run(f'docker build --target oai-formatting-check --tag oai-formatting-check:latest {check_options} --file {lSourcePath}/ci-scripts/docker/Dockerfile.formatting.ubuntu {lSourcePath} > {logDir}/oai-formatting-check.txt 2>&1') + cmd.run(f'docker build --target oai-formatting-check --tag oai-formatting-check:latest {check_options} --file {d}/ci-scripts/docker/Dockerfile.formatting.ubuntu {d} > {logDir}/oai-formatting-check.txt 2>&1') cmd.run('docker image rm oai-formatting-check:latest') cmd.run('docker image prune --force') diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 906f73f433..66e8b2091b 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -22,7 +22,7 @@ import constants as CONST import cls_oaicitest #main class for OAI CI test framework import cls_containerize #class Containerize for all container-based operations on RAN/UE objects -import cls_static_code_analysis #class for static code analysis +import cls_static_code_analysis as SCA import cls_cluster # class for building/deploying on cluster import cls_native # class for all native/source-based operations from cls_ci_helper import TestCaseCtx @@ -64,7 +64,6 @@ def ExecuteActionWithParam(action, ctx, node): global RAN global HTML global CONTAINERS - global SCA global CLUSTER if action == 'Build_eNB' or action == 'Build_Image' or action == 'Build_Proxy' or action == "Build_Cluster_Image" or action == "Build_Run_Tests": RAN.Build_eNB_args=test.findtext('Build_eNB_args') @@ -196,7 +195,7 @@ def ExecuteActionWithParam(action, ctx, node): success = CONTAINERS.Create_Workspace(node, HTML) elif action == 'LicenceAndFormattingCheck': - success = SCA.LicenceAndFormattingCheck(ctx, node, HTML) + success = SCA.StaticCodeAnalysis.LicenceAndFormattingCheck(ctx, node, HTML, RAN.eNBSourceCodePath, RAN.ranBranch, RAN.ranAllowMerge, RAN.ranTargetBranch) elif action == 'Push_Local_Registry': tag_prefix = test.findtext('tag_prefix') or "" @@ -294,7 +293,6 @@ CiTestObj = cls_oaicitest.OaiCiTest() RAN = ran.RANManagement() HTML = cls_oai_html.HTMLManagement() CONTAINERS = cls_containerize.Containerize() -SCA = cls_static_code_analysis.StaticCodeAnalysis() CLUSTER = cls_cluster.Cluster() #----------------------------------------------------------- @@ -304,7 +302,7 @@ CLUSTER = cls_cluster.Cluster() import args_parse # Force local execution, move all execution targets to localhost force_local = False -mode, force_local, date_fmt = args_parse.ArgsParse(sys.argv,CiTestObj,RAN,HTML,CONTAINERS,HELP,SCA,CLUSTER) +mode, force_local, date_fmt = args_parse.ArgsParse(sys.argv,CiTestObj,RAN,HTML,CONTAINERS,HELP,CLUSTER) fmt = "%(levelname)8s: %(message)s" if date_fmt: fmt = "[%(asctime)s] %(levelname)s %(message)s"