blob: 1034b79612848171497db629a59c6f0db435baca [file] [log] [blame]
Basil Eljuse4b14afb2020-09-30 13:07:23 +01001#!/usr/bin/env bash
2
3##############################################################################
Saul Romero7438b3d2024-08-20 16:12:40 +01004# Copyright (c) 2020-2025, ARM Limited and Contributors. All rights reserved.
Basil Eljuse4b14afb2020-09-30 13:07:23 +01005#
6# SPDX-License-Identifier: GPL-2.0-only
7##############################################################################
8
9#==============================================================================
10# FILE: merge.sh
11#
12# DESCRIPTION: Wrapper to merge intermediate json files and LCOV trace .info
13# files.
14#==============================================================================
Saul Romero190b4f12023-12-11 14:22:22 +000015set -x
Basil Eljuse4b14afb2020-09-30 13:07:23 +010016#################################################################
17# Function to manipulate json objects.
18# The json object properties can be accessed through "." separated
19# property names. There are special characters that define a function
20# over a given property value:
21# If the qualifier list starts with '-' then is asking for the len of the
22# json array defined by the qualifiers.
23# If the qualifier list starts with '*' then the resulting json value
24# is returned without double quotes at the end and the beginning.
25# If some property name starts with "?" then is requesting if that
26# property exists within the json object.
27# Globals:
28# None
29# Arguments:
30# 1-Json string that describes the json object
31# 2-String of '.' separated qualifiers to access properties
32# within the json object
33# 3- Optional default value for a sought property value
34# Outputs:
35# None
36################################################################
37get_json_object() {
38 export _json_string="$1"
39 export _qualifiers="$2"
40 export _default="$3"
41 python3 - << EOT
42import os
43import json
44import sys
45
46_json_string = os.getenv("_json_string", "")
47_qualifiers = os.getenv("_qualifiers", "")
48_default = os.getenv("_default", "")
49try:
50 data = json.loads(_json_string)
51except Exception as ex:
52 print("Error decoding json string:{}".format(ex))
53 sys.exit(-1)
54ptr = data
55if _qualifiers[0] in ['-', '*']:
56 cmd = _qualifiers[0]
57 _qualifiers = _qualifiers[1:]
58else:
59 cmd = ""
60for _name in _qualifiers.split("."):
61 if _name in ptr:
62 ptr = ptr[_name]
63 elif _name.isdigit() and int(_name) < len(ptr):
64 ptr = ptr[int(_name)]
65 elif _name.startswith("?"):
66 print(_name[1:] in ptr)
67 sys.exit(0)
68 elif _default:
69 print(_default)
70 sys.exit(0)
71 else:
72 print("'{}' is not in the json object".format(_name))
73 sys.exit(-1)
74if cmd == "-":
75 # return len of the json array
76 print(len(ptr))
77elif cmd == "*":
78 #remove quotes
79 string = json.dumps(ptr)
80 if string.startswith('"') and string.endswith('"'):
81 string = string[1:-1]
82 print(string)
83else:
84 print(json.dumps(ptr))
85EOT
86}
87
88#################################################################
89# Convert a relative path to absolute path
90# Globals:
91# None
92# Arguments:
93# 1-Path to be converted
94# Outputs:
95# Absolute path
96################################################################
97get_abs_path() {
98 path="$1"
99 echo "$(cd $(dirname $path) && echo "$(pwd -P)"/$(basename $path))"
100}
101
102#################################################################
103# Clone the source files
104# Globals:
105# None
106# Arguments:
107# 1-Json file with the sources to be cloned
108# 2-Folder where to clone the sources
109# Outputs:
110# None
111################################################################
112clone_repos() {
113 export OUTPUT_JSON="$1"
114 export CSOURCE_FOLDER="${2:-$LOCAL_WORKSPACE}"
115
116 cd $DIR # To be run at the same level of this script
117python3 - << EOT
118import os
119import clone_sources
120
121output_file = os.getenv('OUTPUT_JSON', 'output_file.json')
122source_folder = os.getenv('CSOURCE_FOLDER', 'source')
123try:
124 r = clone_sources.CloneSources(output_file)
125 r.clone_repo(source_folder)
126except Exception as ex:
127 print(ex)
128EOT
saul-romero-arm6c40b242021-06-18 10:21:04 +0000129 cd -
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100130}
131
132#################################################################
133# Get the a file defined in the json object
134# Globals:
135# None
136# Arguments:
137# 1-Json object that defines the locations of the info and json
138# files
139# 2-Folder to save the info and json files
Saul Romero884d2142023-01-16 10:31:22 +0000140# 3-Reference argument to hold the copied file name location
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100141# Outputs:
142# None
143################################################################
144get_file() {
145 json_object="$1"
146 where="$2"
147 var_name="${3:-param_cloned}" # Defaults to globar var
148
149 local _type=$(get_json_object "$json_object" "type")
150 local _origin=$(get_json_object "$json_object" "*origin")
151 local _compression=$(get_json_object "$json_object" "*compression" None)
152 local fname=""
153 local cloned_file=""
154 local full_filename=$(basename -- "$_origin")
155 local extension="${full_filename##*.}"
156 local filename="${full_filename%.*}"
157
158 if [ "$_type" = '"http"' ];then
159 fname="$where.$extension" # Same filename as folder
160 rm $where/$fname &>/dev/null || true
Saul Romero190b4f12023-12-11 14:22:22 +0000161 wget -nv $_origin -O $where/$fname || return -1
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100162 cloned_file="$(get_abs_path $where/$fname)"
163 elif [ "$_type" = '"bundle"' ];then
164 # Check file exists at origin, i.e. was unbundled before
165 fname="$_origin"
166 if [ -f "$where/$fname" ];then
167 cloned_file="$(get_abs_path $where/$fname)"
168 fi
169 elif [ "$_type" = '"file"' ];then
saul-romero-arm6c40b242021-06-18 10:21:04 +0000170 if [[ "$_origin" = http* ]]; then
171 echo "$_origin looks like 'http' rather than 'file' please check..."
Saul Romero190b4f12023-12-11 14:22:22 +0000172 return -1
saul-romero-arm6c40b242021-06-18 10:21:04 +0000173 fi
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100174 fname="$where.$extension" # Same filename as folder
175 cp -f $_origin $where/$fname
176 cloned_file="$(get_abs_path $where/$fname)"
177 else
178 echo "Error unsupported file type:$_type.... Aborting."
Saul Romero190b4f12023-12-11 14:22:22 +0000179 return -1
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100180 fi
181 if [ "$_compression" = "tar.xz" ];then
182 cd $where
183 pwd
184 tar -xzf $fname
185 rm -f $fname
186 cd -
187 fi
188 eval "${var_name}=${cloned_file}"
Saul Romero190b4f12023-12-11 14:22:22 +0000189 return 0
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100190}
191
192#####################################################################
Saul Romero884d2142023-01-16 10:31:22 +0000193# Get (download/copy) info and json files from the configuration json
194# file
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100195# Globals:
Saul Romero884d2142023-01-16 10:31:22 +0000196# merge_configuration_file: Input json file with locations of info
197# and json scm files to be merged.
198# info_files: Array of info file locations.
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100199# Arguments:
Saul Romero884d2142023-01-16 10:31:22 +0000200# 1: Target folder to download info and json files to be merged.
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100201# Outputs:
202# None
203###################################################################
204get_info_json_files() {
Saul Romero884d2142023-01-16 10:31:22 +0000205 local input_folder="${1:-$LCOV_FOLDER}"
206 local json_string="$(cat $merge_configuration_file)"
207 local config_json_file=""
208 local info_file=""
Saul Romero190b4f12023-12-11 14:22:22 +0000209
210 #printf "\tReading from JSON data:\n\t\t%s" $json_string
Saul Romero884d2142023-01-16 10:31:22 +0000211 # Get files array
212 local nf=$(get_json_object "$json_string" "-files")
213 # Init target folder
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100214 rm -rf $input_folder > /dev/null || true
215 mkdir -p $input_folder
Saul Romero884d2142023-01-16 10:31:22 +0000216 # Iterate through each file element and get the files
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100217 for f in $(seq 0 $(($nf - 1)));
218 do
219 pushd $input_folder > /dev/null
220 _file=$(get_json_object "$json_string" "files.$f")
Saul Romero884d2142023-01-16 10:31:22 +0000221 # The name of the folder is the 'id' value
Saul Romero190b4f12023-12-11 14:22:22 +0000222 id=$(get_json_object "$_file" "*id")
223 tf_config=$(get_json_object "$_file" "*tf-configuration" "N/A")
224 folder=$id
225 printf "Getting files from configuration '$tf_config', build '$folder' into '$input_folder'...\n"
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100226 mkdir -p $folder
227 bundles=$(get_json_object "$_file" "bundles" None)
228 if [ "$bundles" != "None" ];then
229 nb=$(get_json_object "$_file" "-bundles")
230 for n in $(seq 0 $(($nb - 1)));
231 do
232 get_file "$(get_json_object "$bundles" "$n")" $folder
233 done
234 fi
Saul Romero190b4f12023-12-11 14:22:22 +0000235 # Download/copy files and save their locations only if all are found
236 get_file "$(get_json_object "$_file" "config")" $folder config_json_file && \
237 get_file "$(get_json_object "$_file" "info")" $folder info_file && \
238 info_files+=($info_file) && json_files+=($config_json_file) && \
239 list_of_merged_builds+=($id)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100240 popd > /dev/null
241 done
242}
243
244#################################################################
245# Merge json and info files and generate branch coverage report
246# Globals:
Saul Romero884d2142023-01-16 10:31:22 +0000247# merged_coverage_file: Location and name for merged coverage info
248# merged_json_file: Location and name for merged json scm sources
249# LOCAL_WORKSPACE: Local workspace folder with the source code files
250# generate_local: Flag to generate local lcov reports
Saul Romero190b4f12023-12-11 14:22:22 +0000251# info_files: Array of locations and names of info files
252# json_files: Array of locations and names of json files
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100253# Arguments:
Saul Romero884d2142023-01-16 10:31:22 +0000254# 1: Location where reside json and info files
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100255# Outputs:
Saul Romero884d2142023-01-16 10:31:22 +0000256# Merged coverage file
Saul Romero190b4f12023-12-11 14:22:22 +0000257# Merged json configuration file
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100258################################################################
259merge_files() {
Saul Romero190b4f12023-12-11 14:22:22 +0000260# Merge info and json configuration files
261 printf "\tFound report files from %d code coverage folders to be merged...\n" ${#info_files[@]}
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100262 local lc=" "
263 if [ -n "$LOCAL_WORKSPACE" ];then
Saul Romero884d2142023-01-16 10:31:22 +0000264 # Translation from info workspaces into local workspace
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100265 lc=" --local-workspace $LOCAL_WORKSPACE"
266 fi
Saul Romero884d2142023-01-16 10:31:22 +0000267 if [ "$generate_local" = true ];then
268 # Generate local reports
269 lc="${lc} -k"
270 fi
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100271 # Getting the path of the merge.py must reside at the same
272 # path as the merge.sh
273 python3 ${DIR}/merge.py \
Saul Romero190b4f12023-12-11 14:22:22 +0000274 ${info_files[@]/#/-a } \
275 ${json_files[@]/#/-j } \
Saul Romero884d2142023-01-16 10:31:22 +0000276 -o $merged_coverage_file \
277 -m $merged_json_file \
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100278 $lc
279
280}
281
282
283#################################################################
Saul Romero884d2142023-01-16 10:31:22 +0000284# Generate local lcov reports
285# Globals:
286# info_files: Array of locations and names of info files
287# Arguments:
288# None
289# Outputs:
290# Lcov report files for each info file
291################################################################
292generate_local_reports() {
Saul Romero190b4f12023-12-11 14:22:22 +0000293 printf "\tCreating local code coverage reports...\n"
Saul Romero884d2142023-01-16 10:31:22 +0000294 for i in ${!info_files[@]};
295 do
296 local info_file=${info_files[$i]}
297 local parentdir=$(dirname "$info_file")
298 local t_info_file="${info_file/.info/_local.info}"
299 genhtml --branch-coverage $t_info_file \
300 --output-directory $parentdir
301 done
302}
303
304
305#################################################################
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100306# Print scripts usage
307# Arguments:
308# None
309# Outputs:
310# Prints to stdout script usage
311################################################################
312usage() {
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100313help_message=$(cat <<EOF
314
Saul Romero884d2142023-01-16 10:31:22 +0000315# The script merging the info files (code coverage) and json SCM sources
Saul Romero190b4f12023-12-11 14:22:22 +0000316# (intermediate layer) needs a minimum JSON configuration file with the following
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100317# properties:
318# files: array of objects that describe the type of file/project to be
319# merged.
320# id: Unique identifier (project) associated to the info and
321# intermediate json files
322# config: Intermediate json file
323# type: Type of storage for the file. (http or file)
324# origin: Location (url or folder) of the file
325# info: Info file
326# type: Type of storage for the file. (http or file)
327# origin: Location (url or folder) of the file
Saul Romero190b4f12023-12-11 14:22:22 +0000328# <metadata>: Metadata that can be used for print more information related to
329# each project [Optional]
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100330# Example:
331{ "files" : [
332 {
333 "id": "<project 1>",
334 "config":
335 {
336 "type": "http",
337 "origin": "<URL of json file for project 1>"
338 },
339 "info":
340 {
341 "type": "http",
342 "origin": "<URL of info file for project 1>"
Saul Romero190b4f12023-12-11 14:22:22 +0000343 },
344 "metadata": ....
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100345 },
346 {
347 "id": "<project 2>",
348 "config":
349 {
350 "type": "http",
351 "origin": "<URL of json file for project 2>"
352 },
353 "info":
354 {
355 "type": "http",
356 "origin": "<URL of info file for project 2>"
Saul Romero190b4f12023-12-11 14:22:22 +0000357 },
358 "metadata": ....
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100359 },
360 .
361 .
362 .
363 ]
364}
365EOF
366)
Saul Romero190b4f12023-12-11 14:22:22 +0000367 clear || true
Saul ROMERO DOMINGUEZ7bae6202025-04-23 14:25:24 +0000368 echo "Receiving arguments:"
369 echo $@
Saul Romero190b4f12023-12-11 14:22:22 +0000370 echo "Usage:"
371 echo "merge -h Display this help message."
372 echo "-j <JSON filename> JSON configuration file (info and intermediate json filenames to be merged)."
373 echo "[-l <Report path>] Coverage reports directory. Defaults to ./Coverage"
374 echo "[-w <Workspace path>] Workspace directory for source code files."
375 echo "[-o <Info filename>] Merged info file. Defaults to ./merged_coverage.info"
376 echo "[-m <JSON filename>] JSON merged SCM sources. Defaults to ./merged_scm.json"
377 echo "[-c] Flag to download/copy the source files from the JSON merged SCM into the workspace directory."
378 echo "[-g] Flag to generate local reports for each info/json instance."
Saul Romero7438b3d2024-08-20 16:12:40 +0100379 echo "[-i] Ignore errors on genhtml."
380 echo "[-d] Enable debug mode for the script."
Saul Romero190b4f12023-12-11 14:22:22 +0000381 echo "$help_message"
382}
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100383
Saul Romero190b4f12023-12-11 14:22:22 +0000384[ ${-/x} != ${-} ] && TRACING=true || TRACING=false
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100385LOCAL_WORKSPACE=""
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100386CLONE_SOURCES=false
Saul Romero884d2142023-01-16 10:31:22 +0000387merge_configuration_file=""
388generate_local=false
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100389# Folder to to put the reports
Saul Romero884d2142023-01-16 10:31:22 +0000390LCOV_FOLDER="./Coverage"
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100391# File name for merge coverage info
Saul Romero884d2142023-01-16 10:31:22 +0000392merged_coverage_file="./merged_coverage.info"
393merged_json_file="./merged_scm.json"
Saul Romero190b4f12023-12-11 14:22:22 +0000394# File name to pass variables to calling script
395variables_file="./variables.sh"
Saul Romero884d2142023-01-16 10:31:22 +0000396info_files=() # Array of info files
Saul Romero190b4f12023-12-11 14:22:22 +0000397json_files=() # Array of configuration json files
398list_of_merged_builds=()
Saul Romero7438b3d2024-08-20 16:12:40 +0100399GENHTML_ARGS=""
400DEBUG_MODE=false
401genhtml_version=$(genhtml --version | rev | cut -d ' ' -f1 | rev | xargs)
402gen_major=$(echo "$genhtml_version" | cut -d '.' -f1)
403gen_minor=$(echo "$genhtml_version" | rev | cut -d '.' -f1 | rev)
Saul ROMERO DOMINGUEZ7bae6202025-04-23 14:25:24 +0000404unset OPTIND
Saul Romero7438b3d2024-08-20 16:12:40 +0100405while getopts ":hj:o:l:w:idcm:g" opt; do
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100406 case ${opt} in
407 h )
408 usage
409 exit 0
410 ;;
411 w )
Saul Romero190b4f12023-12-11 14:22:22 +0000412 LOCAL_WORKSPACE=$(cd $OPTARG &>/dev/null || true; pwd)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100413 ;;
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100414 c )
415 CLONE_SOURCES=true
416 ;;
Saul Romero7438b3d2024-08-20 16:12:40 +0100417 d )
418 DEBUG_MODE=true
419 ;;
420 i )
421 GENHTML_ARGS="${GENHTML_ARGS} --ignore-errors $([ $gen_major = '2' ] && echo inconsistent || echo source)"
422 ;;
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100423 j )
Saul Romero884d2142023-01-16 10:31:22 +0000424 merge_configuration_file=$OPTARG
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100425 ;;
426 l )
427 LCOV_FOLDER=$OPTARG
428 ;;
429 o )
Saul Romero884d2142023-01-16 10:31:22 +0000430 merged_coverage_file=$OPTARG
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100431 ;;
432 m )
Saul Romero884d2142023-01-16 10:31:22 +0000433 merged_json_file=$OPTARG
434 ;;
435 g )
436 generate_local=true
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100437 ;;
Saul Romero190b4f12023-12-11 14:22:22 +0000438 x )
439 variables_file=$OPTARG
440 ;;
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100441 \? )
442 echo "Invalid option: $OPTARG" 1>&2
443 usage
444 exit -1
445 ;;
446 : )
447 echo "Invalid option: $OPTARG requires an argument" 1>&2
448 usage
449 exit -1
450 ;;
451 esac
452done
Saul Romero7438b3d2024-08-20 16:12:40 +0100453[ $DEBUG_MODE = true ] && set -x || set +x
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100454shift $((OPTIND -1))
Saul Romero884d2142023-01-16 10:31:22 +0000455if [ -z "$merge_configuration_file" ]; then
456 echo "Merged configuration file required."
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100457 usage
458 exit -1
459fi
460if [ -z "$LOCAL_WORKSPACE" ] && [ $CLONE_SOURCES = true ]; then
Saul Romero884d2142023-01-16 10:31:22 +0000461 echo "A local workspace directory is required to clone/copy sources!"
saul-romero-arm6c40b242021-06-18 10:21:04 +0000462 exit -1
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100463fi
Saul Romero884d2142023-01-16 10:31:22 +0000464# Getting the script folder where other qa-tools script files must reside, i.e
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100465# merge.py, clone_sources.py
Saul ROMERO DOMINGUEZ7bae6202025-04-23 14:25:24 +0000466mkdir -p "${LCOV_FOLDER}"
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100467DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
Saul Romero884d2142023-01-16 10:31:22 +0000468LCOV_FOLDER="$(get_abs_path $LCOV_FOLDER)"
469merged_coverage_file="$(get_abs_path $merged_coverage_file)"
470merged_json_file="$(get_abs_path $merged_json_file)"
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100471param_cloned=""
Saul Romero7438b3d2024-08-20 16:12:40 +0100472set +x
473get_info_json_files # always disabled for debug
474[ $DEBUG_MODE = true ] && set -x || set +x
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100475merge_files
476if [ $CLONE_SOURCES = true ];then
Saul Romero884d2142023-01-16 10:31:22 +0000477 clone_repos $merged_json_file
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100478fi
Saul Romero884d2142023-01-16 10:31:22 +0000479
Saul Romero190b4f12023-12-11 14:22:22 +0000480# Generate merged coverage report
481merged_status=true
Saul Romero7438b3d2024-08-20 16:12:40 +0100482genhtml $GENHTML_ARGS --branch-coverage $merged_coverage_file \
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100483 --output-directory $LCOV_FOLDER
Saul Romero190b4f12023-12-11 14:22:22 +0000484if [ $? -ne 0 ];then
485 merged_status=false
486 echo "ERROR: Cannot merge coverage reports"
487fi
Saul Romero884d2142023-01-16 10:31:22 +0000488
489if [ "$generate_local" = true ];then
490 generate_local_reports
491fi