fastlane-plugin-code_static_analyzer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ #!/bin/bash -o pipefail
2
+ #!/bin/sh -e
3
+ # run_script.sh
4
+ # @desc Run shell script.
5
+ # @usage
6
+ # Parameters
7
+ # $1 - shell script you want to run
8
+ # $2 - filename with extension to store command results
9
+
10
+ {
11
+ eval $1 | tee "$2"
12
+ } &> /dev/null
13
+ exit $?
@@ -0,0 +1,17 @@
1
+ require 'fastlane/plugin/code_static_analyzer/version'
2
+
3
+ module Fastlane
4
+ module CodeStaticAnalyzer
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
7
+ def self.all_classes
8
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
9
+ end
10
+ end
11
+ end
12
+
13
+ # By default we want to import all available actions and helpers
14
+ # A plugin can contain any number of actions and plugins
15
+ Fastlane::CodeStaticAnalyzer.all_classes.each do |current|
16
+ require current
17
+ end
@@ -0,0 +1,310 @@
1
+ module Fastlane
2
+ require File.join CodeStaticAnalyzer::ROOT, "assets/formatter.rb"
3
+ require File.join CodeStaticAnalyzer::ROOT, "assets/junit_parser.rb"
4
+ require 'xcodeproj'
5
+
6
+ module Actions
7
+ module SharedValues
8
+ ANALYZER_STATUS = :ANALYZER_STATUS
9
+ end
10
+
11
+ class CodeStaticAnalyzerAction < Action
12
+ SUPPORTED_ANALYZER = ["xcodeWar", "rubocop", "CPD"]
13
+ RESULT_FILE = 'codeAnalysResults_analyzers.xml'
14
+ attr_accessor :checked_pmd, :checked_xcode_param, :xml_content, :run_main
15
+
16
+ def self.run(params)
17
+ @run_main = true
18
+ Actions::CodeStaticAnalyzerAction.is_pmd_installed
19
+ platform = Actions.lane_context[SharedValues::PLATFORM_NAME].to_s
20
+ @xml_content = ''
21
+ root_dir = work_dir
22
+ analyzers = params[:analyzers]
23
+ analyzers = SUPPORTED_ANALYZER if (analyzers and analyzers.empty?) or analyzers[0] == 'all'
24
+ xcode_project = params[:xcode_project_name]
25
+ xcode_workspace = params[:xcode_workspace_name]
26
+ xcode_targets = params[:xcode_targets]
27
+ # use additional checks for optional parameters, but required in specific analyzer
28
+ analyzers.each do |analyzer|
29
+ case analyzer.downcase
30
+ when 'xcodewar'
31
+ UI.user_error!("No project name for Warnings Analyzer given. Pass using `xcode_project` or configure analyzers to run using `analyzers`") if !xcode_project or (xcode_project and xcode_project.empty?) and platform != 'android'
32
+ checked_params = xcode_check_parameters(root_dir, xcode_project, xcode_workspace, xcode_targets)
33
+ xcode_project = checked_params[0]
34
+ xcode_workspace = checked_params[1]
35
+ xcode_targets = checked_params[2]
36
+ end
37
+ end
38
+
39
+ status_rubocop = 0
40
+ status_static = 0
41
+ clear_all_files = "#{root_dir}#{params[:result_dir]}/*.*"
42
+ # clear_temp_files = "#{root_dir}#{params[:result_dir]}/*temp*.*"
43
+ sh "rm -rf #{clear_all_files}"
44
+
45
+ # Run alyzers
46
+ status_cpd = Actions::CpdAnalyzerAction.run(
47
+ result_dir: params[:result_dir],
48
+ tokens: params[:cpd_tokens],
49
+ language: params[:cpd_language],
50
+ cpd_files_to_inspect: params[:cpd_files_to_inspect],
51
+ cpd_files_to_exclude: params[:cpd_files_to_exclude]
52
+ )
53
+ analyzers.each do |analyzer|
54
+ case analyzer.downcase
55
+ when 'xcodewar'
56
+ if platform != "android"
57
+ status_static = Actions::WarningAnalyzerAction.run(
58
+ result_dir: params[:result_dir],
59
+ xcode_project_name: xcode_project,
60
+ xcode_workspace_name: xcode_workspace,
61
+ xcode_targets: xcode_targets
62
+ )
63
+ end
64
+ when 'rubocop'
65
+ status_rubocop = Actions::RubyAnalyzerAction.run(
66
+ result_dir: params[:result_dir],
67
+ ruby_files: params[:ruby_files]
68
+ )
69
+ end
70
+ end
71
+ # sh "rm -rf #{clear_temp_files}"
72
+
73
+ create_analyzers_run_result("#{root_dir}#{params[:result_dir]}/") unless @xml_content.empty?
74
+
75
+ if Actions::CodeStaticAnalyzerAction.status_to_boolean(status_cpd) &&
76
+ Actions::CodeStaticAnalyzerAction.status_to_boolean(status_static) &&
77
+ Actions::CodeStaticAnalyzerAction.status_to_boolean(status_rubocop)
78
+ Actions.lane_context[SharedValues::ANALYZER_STATUS] = true
79
+ else
80
+ Actions.lane_context[SharedValues::ANALYZER_STATUS] = false
81
+ end
82
+ end
83
+
84
+ def self.start_xml_content
85
+ @xml_content = ""
86
+ end
87
+
88
+ def self.run_from_main_action
89
+ @run_main
90
+ end
91
+
92
+ def self.analyzers_xml
93
+ @xml_content
94
+ end
95
+
96
+ class << self
97
+ attr_reader :checked_pmd
98
+ end
99
+
100
+ def self.checked_xcode
101
+ @checked_xcode_param
102
+ end
103
+
104
+ def self.add_xml_content(root, analyzer_name, temp_result_file, info)
105
+ UI.error "#{analyzer_name} analyzer failed to create temporary result file. More info in #{root}#{RESULT_FILE}"
106
+ new_line = JunitParser.xml_level(3)
107
+ if info.empty?
108
+ info = "Don't see #{temp_result_file} file.#{new_line}Try to run command with --verbose" \
109
+ " to see warnings made by used analyzers"
110
+ end
111
+ failures = JunitParser.add_failure('', 'unexisted result file', "#{new_line}#{info}")
112
+ @xml_content += JunitParser.add_failed_testcase("#{analyzer_name} analyzer crash", failures)
113
+ end
114
+
115
+ def self.create_analyzers_run_result(result_dir)
116
+ unless @xml_content.empty?
117
+ junit_xml = JunitParser.add_testsuite('static anlyzers', @xml_content)
118
+ JunitParser.create_junit_xml(junit_xml, "#{result_dir}/#{RESULT_FILE}")
119
+ end
120
+ end
121
+
122
+ def self.status_to_boolean(var)
123
+ if var == 0 or var == '0' or var == true or var == 'true'
124
+ return true
125
+ else
126
+ return false
127
+ end
128
+ end
129
+
130
+ def self.is_pmd_installed
131
+ @checked_pmd = false
132
+ begin
133
+ Actions.sh('type pmd')
134
+ rescue
135
+ UI.user_error! 'PMD not installed. Please, install PMD for using copy paste analyzer.'
136
+ end
137
+ @checked_pmd = true
138
+ end
139
+
140
+ def self.work_dir
141
+ directory = Dir.pwd
142
+ directory + "/" unless directory.end_with? "/"
143
+ end
144
+
145
+ def self.check_file_exist(work_dir, file, parameter_name)
146
+ if file.kind_of?(Array)
147
+ file.each do |file_path|
148
+ UI.user_error!("Unexisted path '#{work_dir}#{file_path}'. Check '#{parameter_name}' parameter. Files should be relative to work directory '#{work_dir}'") if Dir.glob("#{work_dir}#{file_path}").empty?
149
+ end
150
+ else
151
+ UI.user_error!("Unexisted path '#{work_dir}#{file}'. Check '#{parameter_name}' parameter. Files should be relative to work directory '#{work_dir}'") if Dir.glob("#{work_dir}#{file}").empty?
152
+ end
153
+ end
154
+
155
+ def self.add_root_path(root, file_list, is_inspected)
156
+ file_list_str = ''
157
+ if file_list.nil? || file_list.empty?
158
+ file_list_str = "'#{root}'" if is_inspected
159
+ else
160
+ file_list.each do |file|
161
+ file_path = "#{root}#{file}"
162
+ file_path = file_path.sub("//", '/')
163
+ file_path = file_path.sub("/./", '/')
164
+ file_list_str += "'#{file_path}' "
165
+ end
166
+ end
167
+ file_list_str
168
+ end
169
+
170
+ def self.xcode_check_parameters(root_dir, project, workspace, targets)
171
+ @checked_xcode_param = false
172
+ if Actions.lane_context[SharedValues::PLATFORM_NAME] == 'android'
173
+ UI.user_error! 'This warning_analyzer not supported for ios platform'
174
+ else
175
+ unless project.empty?
176
+ project += '.xcodeproj' unless project.end_with? '.xcodeproj'
177
+ wrong_path = Dir.glob(root_dir + project).empty?
178
+ UI.user_error! "Wrong project name '#{project}'. Check extension if you use it." if wrong_path
179
+ end
180
+
181
+ if workspace and !workspace.empty?
182
+ workspace += '.xcworkspace' unless workspace.end_with? '.xcworkspace'
183
+ else
184
+ workspace = project.gsub(".xcodeproj", '') + '.xcworkspace'
185
+ end
186
+ wrong_path = Dir.glob(root_dir + workspace).empty?
187
+ UI.user_error! "Wrong workspace name '#{workspace}'" if wrong_path
188
+
189
+ # check targets
190
+ available_targets = []
191
+ new_targets = []
192
+ project_info = Xcodeproj::Project.open(project.to_s)
193
+ project_info.targets.each do |target|
194
+ available_targets.push(target.name)
195
+ end
196
+ if targets and targets.count > 0
197
+ error = []
198
+ targets.each do |currect_target|
199
+ next if currect_target.tr(' ', '').empty?
200
+ if available_targets.include? currect_target
201
+ new_targets.push(currect_target)
202
+ else
203
+ error.push(currect_target)
204
+ end
205
+ end
206
+ UI.user_error!("The #{error} targets don't exist in project.") unless error.count == 0
207
+ end
208
+ new_targets = available_targets if new_targets.count == 0
209
+ @checked_xcode_param = true
210
+ [project, workspace, new_targets]
211
+ end
212
+ end
213
+
214
+ def self.description
215
+ "Runs different Static Analyzers and generate report"
216
+ end
217
+
218
+ def self.authors
219
+ ["olgakn"]
220
+ end
221
+
222
+ def self.return_value
223
+ # If your method provides a return value, you can describe here what it does
224
+ end
225
+
226
+ def self.details
227
+ # Optional:
228
+ "This plugins is the helper for checking code on warnings, copypaste, syntax, etc.\n" \
229
+ "Each analyzer in this plugin save result status in shared values <NAME>_ANALYZER_STATUS: 0 - code is clear, any other value - code include warnings/errors.\n" \
230
+ "Also each analyzer save results in separate file: codeAnalysResult_<name of analyzer>.xml"
231
+ end
232
+
233
+ def self.available_options
234
+ [
235
+ FastlaneCore::ConfigItem.new(key: :analyzers,
236
+ env_name: "FL_CSA_RUN_ANALYZERS",
237
+ description: "List of analysers you want to run. Supported analyzers: #{SUPPORTED_ANALYZER}",
238
+ optional: false,
239
+ type: Array,
240
+ verify_block: proc do |value|
241
+ UI.message '[!] Will be run all analyzers'.blue if (value and value.empty?) or value[0] == 'all'
242
+ value.each do |run_analyzer|
243
+ UI.user_error!("The analyzer '#{run_analyzer}' is not supported. Supported analyzers: #{SUPPORTED_ANALYZER}, 'all'") unless SUPPORTED_ANALYZER.map(&:downcase).include? run_analyzer.downcase or run_analyzer == 'all'
244
+ end
245
+ end),
246
+ FastlaneCore::ConfigItem.new(key: :result_dir,
247
+ env_name: "CSA_RESULT_DIR_NAME",
248
+ description: "Directory's name for storing analysis results",
249
+ optional: true,
250
+ type: String,
251
+ default_value: 'artifacts'),
252
+ # parameters for CPD analyzer
253
+ FastlaneCore::ConfigItem.new(key: :cpd_tokens,
254
+ description: "The min number of words in code that is detected as copy paste",
255
+ optional: true,
256
+ type: String,
257
+ default_value: '100'),
258
+ FastlaneCore::ConfigItem.new(key: :cpd_files_to_inspect,
259
+ description: "List of files and directories (relative to work directory) to inspect on copy paste",
260
+ optional: true,
261
+ type: Array),
262
+ FastlaneCore::ConfigItem.new(key: :cpd_files_to_exclude,
263
+ description: "List of files and directories (relative to work directory) not to inspect on copy paste",
264
+ optional: true,
265
+ type: Array),
266
+ FastlaneCore::ConfigItem.new(key: :cpd_language,
267
+ description: "Language used in files that will be inspected on copy paste.\nSupported analyzers: #{Actions::CpdAnalyzerAction::SUPPORTED_LAN} or don't set if you need any other language",
268
+ optional: true,
269
+ type: String,
270
+ verify_block: proc do |value|
271
+ UI.user_error!("Language '#{value}' is not supported.\nSupported languages: #{Actions::CpdAnalyzerAction::SUPPORTED_LAN} or empty if you need any other language") unless Actions::CpdAnalyzerAction::SUPPORTED_LAN.map(&:downcase).include? value.downcase or value.empty? or !value
272
+ end),
273
+ # parameters for Ruby analyzer
274
+ FastlaneCore::ConfigItem.new(key: :ruby_files,
275
+ description: "List of path (relative to work directory) to ruby files to be inspected on warnings & syntax",
276
+ optional: true,
277
+ type: Array),
278
+ # parameters for Warnings analyzer
279
+ FastlaneCore::ConfigItem.new(key: :xcode_project_name, # required in analyzer
280
+ description: "Xcode project name in work directory",
281
+ optional: true,
282
+ type: String),
283
+ FastlaneCore::ConfigItem.new(key: :xcode_workspace_name,
284
+ description: "Xcode workspace name in work directory",
285
+ optional: true,
286
+ type: String),
287
+ FastlaneCore::ConfigItem.new(key: :xcode_targets,
288
+ description: "List of Xcode targets to inspect",
289
+ optional: true,
290
+ type: Array)
291
+ ]
292
+ end
293
+
294
+ def self.output
295
+ # Define the shared values you are going to provide
296
+ [
297
+ ['ANALYZER_STATUS', 'Code analysis result (0 - code is clear, any other value - code include warnings/errors/etc.)']
298
+ ]
299
+ end
300
+
301
+ def self.is_supported?(platform)
302
+ # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
303
+ # See: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md
304
+ #
305
+ # [:ios, :mac, :android].include?(platform)
306
+ true
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,134 @@
1
+ module Fastlane
2
+ module Actions
3
+ module SharedValues
4
+ CPD_ANALYZER_STATUS = :CPD_ANALYZER_STATUS
5
+ end
6
+
7
+ class CpdAnalyzerAction < Action
8
+ SUPPORTED_LAN = ['python', 'objectivec', 'jsp', 'ecmascript', 'fortran', 'cpp', 'ruby', 'php', 'java', 'matlab', 'scala', 'plsql', 'go', 'cs']
9
+
10
+ def self.run(params)
11
+ UI.header 'CPD analyzer' if Actions::CodeStaticAnalyzerAction.run_from_main_action
12
+ Actions::CodeStaticAnalyzerAction.is_pmd_installed unless Actions::CodeStaticAnalyzerAction.checked_pmd
13
+ work_dir = Actions::CodeStaticAnalyzerAction.work_dir
14
+
15
+ # checking files for analysing
16
+ files_to_exclude = params[:cpd_files_to_exclude]
17
+ files_to_inspect = params[:cpd_files_to_inspect]
18
+ UI.message '[!] CPD analyzer will be run for all files in work directory'.blue if !files_to_inspect or files_to_inspect.empty?
19
+ Actions::CodeStaticAnalyzerAction.check_file_exist(work_dir, files_to_exclude, 'cpd_files_to_exclude') if files_to_exclude
20
+ Actions::CodeStaticAnalyzerAction.check_file_exist(work_dir, files_to_inspect, 'cpd_files_to_inspect') if files_to_inspect
21
+
22
+ # prepare script and metadata for saving results
23
+ result_dir_path = "#{work_dir}#{params[:result_dir]}"
24
+ FileUtils.mkdir_p(result_dir_path) unless File.exist?(result_dir_path)
25
+ temp_result_file = "#{result_dir_path}/temp_copypaste.xml"
26
+ result_file = "#{result_dir_path}/codeAnalysResults_cpd.xml"
27
+ tokens = params[:tokens]
28
+ files = Actions::CodeStaticAnalyzerAction.add_root_path(work_dir, files_to_inspect, true)
29
+ lan = params[:language]
30
+ exclude_files = Actions::CodeStaticAnalyzerAction.add_root_path(work_dir, files_to_exclude, false)
31
+ run_script = " pmd cpd --minimum-tokens #{tokens} --files #{files}"
32
+ run_script += " --exclude #{exclude_files}" unless exclude_files == ''
33
+ run_script += " --language #{lan}" unless (lan and lan.empty?) or !lan
34
+ run_script += " --format xml"
35
+ run_script_path = File.join CodeStaticAnalyzer::ROOT, "assets/run_script.sh"
36
+ run_script = "#{run_script_path} \"#{run_script}\" '#{temp_result_file}'"
37
+ # use analyzer
38
+ Formatter.cpd_format(tokens, lan, exclude_files, temp_result_file, files)
39
+ FastlaneCore::CommandExecutor.execute(command: run_script.to_s,
40
+ print_all: false,
41
+ error: proc do |error_output|
42
+ # handle error here
43
+ end)
44
+ status = $?.exitstatus
45
+
46
+ # prepare results
47
+ if Dir.glob(temp_result_file).empty? or status == 1
48
+ Actions::CodeStaticAnalyzerAction.start_xml_content unless Actions::CodeStaticAnalyzerAction.run_from_main_action
49
+ info = (status == 1) ? "CPD return 1: Couldn't understand command line parameters or CPD exited with an exception" : ''
50
+ Actions::CodeStaticAnalyzerAction.add_xml_content("#{result_dir_path}/", 'Copy paste', temp_result_file, info)
51
+ Actions::CodeStaticAnalyzerAction.create_analyzers_run_result("#{result_dir_path}/") unless Actions::CodeStaticAnalyzerAction.run_from_main_action
52
+ status = 43
53
+ else
54
+ status = 0 if File.read(temp_result_file).empty?
55
+ xml_content = JunitParser.parse_xml(temp_result_file)
56
+ junit_xml = JunitParser.add_testsuite('copypaste', xml_content)
57
+ JunitParser.create_junit_xml(junit_xml, result_file)
58
+ end
59
+ Actions.lane_context[SharedValues::CPD_ANALYZER_STATUS] = status
60
+ end
61
+
62
+ #####################################################
63
+ # @!group Documentation
64
+ #####################################################
65
+
66
+ def self.description
67
+ "This analyzer detect copy paste code (it uses PMD CPD)"
68
+ end
69
+
70
+ def self.details
71
+ # Optional:
72
+ # this is your chance to provide a more detailed description of this action
73
+ "Important: install PMD if you want to use copy paste detector\n" \
74
+ "Important: Always use 'language' parameter except the needed language isn't available in list of supported languages"
75
+ end
76
+
77
+ def self.available_options
78
+ # Define all options your action supports.
79
+ [
80
+ FastlaneCore::ConfigItem.new(key: :result_dir,
81
+ env_name: "FL_CPD_ANALYZER_RESULT_DIR",
82
+ description: "Directory's name for storing analysis results",
83
+ optional: true,
84
+ type: String,
85
+ default_value: 'artifacts'),
86
+ FastlaneCore::ConfigItem.new(key: :tokens,
87
+ env_name: "FL_CPD_ANALYZER_TOKENS",
88
+ description: "The min number of words in code that is detected as copy paste",
89
+ optional: true,
90
+ type: String,
91
+ default_value: '100'),
92
+ FastlaneCore::ConfigItem.new(key: :cpd_files_to_inspect,
93
+ env_name: "FL_CPD_ANALYZER_FILES_TO_INSPECT",
94
+ description: "List of files and directories (relative to work directory) to inspect on copy paste",
95
+ optional: true,
96
+ type: Array),
97
+ FastlaneCore::ConfigItem.new(key: :cpd_files_to_exclude,
98
+ env_name: "FL_CPD_ANALYZER_FILES_NOT_TO_INSPECT",
99
+ description: "List of files and directories (relative to work directory) not to inspect on copy paste",
100
+ optional: true,
101
+ type: Array),
102
+ FastlaneCore::ConfigItem.new(key: :language,
103
+ env_name: "FL_CPD_ANALYZER_FILE_LANGUAGE",
104
+ description: "Language used in files that will be inspected on copy paste.\nSupported analyzers: #{SUPPORTED_LAN} or don't set if you need any other language",
105
+ optional: true,
106
+ type: String,
107
+ verify_block: proc do |value|
108
+ UI.user_error!("This language is not supported. Supported languages: #{SUPPORTED_LAN} or empty if you need any other language") unless SUPPORTED_LAN.map(&:downcase).include? value.downcase or value.empty? or !value
109
+ end)
110
+ ]
111
+ end
112
+
113
+ def self.output
114
+ # Define the shared values you are going to provide
115
+ [
116
+ ['CPD_ANALYZER_STATUS', 'Copy paste analyzer result status (0 - success, any other value - failed)']
117
+ ]
118
+ end
119
+
120
+ def self.return_value
121
+ # If you method provides a return value, you can describe here what it does
122
+ end
123
+
124
+ def self.authors
125
+ # So no one will ever forget your contribution to fastlane :) You are awesome btw!
126
+ ["olgakn"]
127
+ end
128
+
129
+ def self.is_supported?(platform)
130
+ true
131
+ end
132
+ end
133
+ end
134
+ end