#!/usr/bin/env ruby require 'fileutils' $:.unshift(File.dirname(__FILE__)+"/../lib") require "bakeqac/options/options" require "bakeqac/filter" require 'bake/toolchain/colorizing_formatter' require 'bake/options/options' require 'common/process' require 'common/utils' require 'common/ext/file' STDOUT.sync = true STDERR.sync = true module Bake @@cipFileSize = 1 def self.checkCipSize() cips = Dir.glob(@options.qacdata + "/**/*.cip") if !cips.empty? s = File.size?(cips[0]) s = 0 if s.nil? if s < @@cipFileSize puts "cip file too small: #{cips[0]} has #{s} bytes" return false end @@cipFileSize = s return true end puts "cip file does not exist!" return false end def self.executeQacli(cmd, adminStepAndImmediateOutput = false) timeStart = Time.now cSizeCheck = true licenseError = false qacDaemonError = false consoleOutput = "" success = false loop do FileUtils.rm_rf @options.qacdata if adminStepAndImmediateOutput success, consoleOutput = ProcessHelper.run(cmd, adminStepAndImmediateOutput) licenseError = false qacDaemonError = false consoleOutput.each_line do |line| if (line.include?("License Refused") && !line.include?("License Refused: C:")) licenseError = true puts "License refused!" break end if (line.include?("unable to connect to QAX Daemon") && !line.include?("License Refused: C:")) qacDaemonError = true puts "Unable to connect to QAX Daemon!" break end end cSizeCheck = checkCipSize() break unless ((@options.qacretry >= (Time.now - timeStart)) && (!cSizeCheck || licenseError || qacDaemonError)) puts "Retry seconds left: %d" % (@options.qacretry - (Time.now - timeStart)) end checkError = !cSizeCheck || licenseError || qacDaemonError if @options.qacretry > 0 && checkError puts "Retry timeout over: %d -> failure." % (@options.qacretry - (Time.now - timeStart)) end success = false if checkError return [success, consoleOutput, checkError] end ###### PREREQUISITE 1: BAKEQAC OPTIONS ###### @options = BakeqacOptions.new(ARGV) bakeOptions = Options.new([]) @options.parse_options(bakeOptions) pfilter = ProjectFilter.new(@options) success = false ###### PREREQUISITE 2: BAKE OPTIONS ###### passedParams = [] excludeParam = false wasMinus = false ARGV.each do |x| if ["--c++11", "--c++14", "--qacnofilter", "--qacnoformat", "--qacrawformat", "--qacunittest", "--qacdoc", "--qaccctpatch"].include?x excludeParam = false next elsif ["--cct", "--rcf", "--acf", "--qacdata", "--qacstep", "--qacretry"].include?x excludeParam = true next elsif excludeParam excludeParam = false next end passedParams << x end passedParams << "--rebuild" unless passedParams.include?"--rebuild" passedParams << "--compile-only" if (passedParams & ["--compile-only", "--compile_only", "-f"]).empty? passedParams << "--filter-paths" unless passedParams.include?"--filter-paths" passedParams << "--qac" unless passedParams.include?"--qac" success = true ###### PREREQUISITE 3: QACLI LOCATION ###### qaExe = [@options.qac_home+"/common/bin/qacli"] if @options.qacunittest qaExe = ["ruby", File.dirname(__FILE__)+"/../spec/bin/qactester.rb"] end ###### STEP 1: CREATE ###### if (@options.qacstep.nil? or @options.qacstep.include?"admin") cmd = qaExe + ["admin", "--qaf-project-config", "--qaf-project", @options.qacdata] @options.cct.each {|c| cmd << "--cct" << c } cmd << "--rcf" << @options.rcf cmd << "--acf" << @options.acf puts "bakeqac: creating database..." success, consoleOutput, checkError = executeQacli(cmd, true) if success cctFilename = @options.qacdata+"/prqa/config/"+File.basename(@options.cct[0]) if File.exist?cctFilename File.open(cctFilename, 'a') do |f| if @options.cct_patch f << "-coderegex='s/__attribute__\\s*\\(.*\\)//'\n" f << "-d __cdecl=\n" f << "-d _cdecl=\n" f << "-d _thiscall=\n" f << "-d __stdcall=\n" end if @options.cct_append File.open(@options.cct_append, 'r') do |a| a.each_line { |line| f << line } end end end end else puts consoleOutput end end ###### STEP 2: BUILD ###### if success and (@options.qacstep.nil? or @options.qacstep.include?"analyze") FileUtils::mkdir_p @options.qacdata cmd = qaExe + ["analyze", "-P", @options.qacdata, "-b"] begin devMode = File.exist?"c:/Projekte/git/bake/bin/bake" rescue Exception devMode = false end if devMode bcmd = "ruby c:/Projekte/git/bake/bin/bake " else bcmd = (Utils::OS.windows? ? "cmd /c bake.bat " : "bake ") end bcmd += passedParams.join(" ") cmd << bcmd puts "bakeqac: building and analyzing files..." success, consoleOutput, checkError = executeQacli(cmd) success = false # we have to parse the output, qacli returns always an error here... if !checkError filterOutput = [] filter = [] endFound = false consoleOutput.each_line do |line| scan_res = line.scan(/Project path: ([a-zA-Z]{0,1})(:{0,1})(.*)/) if scan_res.length > 0 filter << (scan_res[0][0].downcase + scan_res[0][1] + scan_res[0][2].gsub(/\\/,"/").strip) elsif !endFound filterOutput << line if line.start_with?("Rebuilding ") endFound = true success = true if line.include?("Rebuilding done") # don't know why the return value is 2 in this case... end end end end if @options.qacfilter if success puts filterOutput ProjectFilter.writeFilter(filter) else puts consoleOutput # error end else puts consoleOutput # no filter end end ###### STEP 3: RESULT ###### if success and (@options.qacstep.nil? or @options.qacstep.include?"view") FileUtils::mkdir_p @options.qacdata puts "bakeqac: printing results..." cmd = qaExe + ["view", "-P", @options.qacdata, "-m", "STDOUT"] if not @options.qacnoformat cmd += ["-f", "%?u==0%(MSG: %:trc: %)%F(%l,%c): (%r:%N)%t%?v%(\n%v%)"] end success, consoleOutput, checkError = executeQacli(cmd) if ProjectFilter.is_valid? if !checkError # success # HACK: seems that QAC returns with error if there too much issues # 1. filter filteredLines = [] foundFile = false consoleOutput.each_line do |line| line.strip! foundFile = false if line.empty? or line.include? " ======= Results for " scan_res = line.scan(/\/\/ ======= Results for ([a-zA-Z]{0,1})(:{0,1})(.*)/) if scan_res.length > 0 converted_line = (scan_res[0][0].downcase + scan_res[0][1] + scan_res[0][2].gsub(/\\/,"/")) foundFile = ProjectFilter.localFile(converted_line) end if foundFile && !line.include?("QAC++ Deep Flow Static Analyser") filteredLines << line elsif !success # HACK, see above filteredLines << line if ["MSG: ", "trc: ", "// ======= Results for"].none? { |pattern| line.include?(pattern) } end end # 2. sort sortedLines = [] linesOfFile = {} lineNum = 0 currentMessage = [] filteredLines.each do |line| if line.include? " ======= Results for " lineNum = 0 linesOfFile.sort.each { |lineNr, s| sortedLines += s } linesOfFile = {} sortedLines << line else if (!@options.qacnoformat && line.start_with?("MSG: ")) || (@options.qacnoformat && line.include?(": Msg(")) lineScan = line.scan(/[^\(]*\((\d*)/) lineNum = lineScan[0][0].to_i if lineScan.length > 0 end if linesOfFile.has_key?lineNum currentMessage = linesOfFile[lineNum] else currentMessage = [] linesOfFile[lineNum] = currentMessage end currentMessage << line end end linesOfFile.sort.each { |lineNr, s| sortedLines += s } # 3. print numberOfMessages = 0 Dir.chdir(@options.qac_home) do sortedLines.each do |line| if (!@options.qacnoformat && line.start_with?("MSG: ")) || (@options.qacnoformat && line.include?(": Msg(")) Bake.formatter.printWarning(line) if @options.qacdoc errorNumScan = line.scan(/[^\(]*\([^\(]*\([^:]*:(\d*)/) if errorNumScan.length > 0 errorNum = errorNumScan[0][0].to_i htmlFiles = Dir.glob("components/qacpp*/doc-en_US/messages/%04d.html" % errorNum) + Dir.glob("components/mcpp*/doc-en_US/messages/%04d.html" % errorNum) htmlFiles.each do |htmlFile| puts "doc: #{File.expand_path(htmlFile)}" end end end numberOfMessages += 1 elsif line.include? " ======= Results for " Bake.formatter.printAdditionalInfo(line) else Bake.formatter.printInfo(line) end end Bake.formatter.printSuccess("\n**** Number of messages: #{numberOfMessages} ****") if !success # HACK, see above Bake.formatter.printInfo("\nNote: qacli exited with error, maybe the number of messages was too high and output was truncated.") success = true end end else puts consoleOutput # error end else puts consoleOutput # no filter end end ###### STEP 4a: REPORT SCRIPT CHECK (OPTIONAL) ###### if success and !@options.qacstep.nil? and @options.qacstep.include?("report") puts "bakeqac: preparing reports..." # check if adapted scriptsAdapted = true [@options.qac_home+"/report_plugins/Rule_Compliance_Report.py", @options.qac_home+"/report_plugins/Suppressions_Report.py"].each do |script| if !File.exist?script puts "Error: script #{script} not found" success = false break end foundVersion = false File.open(script, "r").each_line do |line| scan_res = line.scan(/v(\d+\.\d+): Adapted.+_Report\.py/) if scan_res.length > 0 puts "Info: script #{script} has version \"#{scan_res[0][0]}\"" foundVersion = true break end end puts "Info: script #{script} not adapted" if foundVersion == false end end ###### STEP 4b: REPORT SUR (OPTIONAL) ###### if success and !@options.qacstep.nil? and @options.qacstep.include?("report") FileUtils::mkdir_p @options.qacdata puts "bakeqac: generating SUR report..." cmd = qaExe + ["report", "-P", @options.qacdata, "-t", "SUR"] success, consoleOutput = ProcessHelper.run(cmd, false) puts consoleOutput end ###### STEP 4c: REPORT RCR (OPTIONAL) ###### if success and !@options.qacstep.nil? and @options.qacstep.include?("report") FileUtils::mkdir_p @options.qacdata puts "bakeqac: generating RCR report..." cmd = qaExe + ["report", "-P", @options.qacdata, "-t", "RCR"] success, consoleOutput = ProcessHelper.run(cmd, false) puts consoleOutput end ###### STEP 5: REPORT MDR (OPTIONAL) ###### if success and !@options.qacstep.nil? and @options.qacstep.include?("mdr") FileUtils::mkdir_p @options.qacdata puts "bakeqac: generating MDR report..." cmd = qaExe + ["report", "-P", @options.qacdata, "-t", "MDR"] success, consoleOutput = ProcessHelper.run(cmd, false) puts consoleOutput if success require "json" jsons = Dir.glob(@options.qacdata + "/prqa/reports/data/*.json") maxComplexity = 0 numGreater10 = 0 jsons.each do |file| raw = File.read(file) data = JSON.parse(raw) filename = File.normalize(data["file"]) if ProjectFilter.localFile(filename) Bake.formatter.printAdditionalInfo(filename) entities = data["entities"] if Array === entities entities.each do |e| if e.has_key?("type") && e["type"] == "function" if e.has_key?("metrics") && e["metrics"].has_key?("STCYC") complexity = e["metrics"]["STCYC"].to_i str = " #{e["name"]}:#{e["line"]}: cyclomatic complexity = #{complexity}" complexity > 10 ? Bake.formatter.printWarning(str) : puts(str) maxComplexity = complexity if complexity > maxComplexity numGreater10 +=1 if complexity > 10 end end end end end end Bake.formatter.printInfo("\n**** Maximum cyclomatic complexity: #{maxComplexity} ****") resultStr = "**** Number of functions with cyclomatic complexity > 10: #{numGreater10} ****" numGreater10 > 0 ? Bake.formatter.printWarning(resultStr) : Bake.formatter.printSuccess(resultStr) else Bake.formatter.printError("Failed to generate MDR report.") end end #### TODO: unittest for report ###### DONE ###### exit(success ? 0 : 1) end