summaryrefslogtreecommitdiff
path: root/scripts/static-analysis
blob: 7aa2fbf15949991f32c0a05e75748b4ccdc0791b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/env ruby
# frozen_string_literal: true

# We don't have auto-loading here
require_relative '../lib/gitlab'
require_relative '../lib/gitlab/popen'
require_relative '../lib/gitlab/popen/runner'

class StaticAnalysis
  ALLOWED_WARNINGS = [
    # https://github.com/browserslist/browserslist/blob/d0ec62eb48c41c218478cd3ac28684df051cc865/node.js#L329
    # warns if caniuse-lite package is older than 6 months. Ignore this
    # warning message so that GitLab backports don't fail.
    "Browserslist: caniuse-lite is outdated. Please run next command `yarn upgrade`"
  ].freeze

  # `gettext:updated_check` and `gitlab:sidekiq:sidekiq_queues_yml:check` will fail on FOSS installations
  # (e.g. gitlab-org/gitlab-foss) since they test against a single
  # file that is generated by an EE installation, which can
  # contain values that a FOSS installation won't find. To work
  # around this we will only enable this task on EE installations.
  TASKS_BY_DURATIONS_SECONDS_DESC = {
    %w[bin/rake lint:haml] => 488,
    (Gitlab.ee? ? %w[bin/rake gettext:updated_check] : nil) => 410,
    # Most of the time, RuboCop finishes in 30 seconds, but sometimes it can take around 1200 seconds so we set a
    # duration of 300 to lower the likelihood that it will run in the same job as another long task...
    %w[bundle exec rubocop --parallel] => 300,
    %w[yarn run lint:eslint:all] => 264,
    %w[yarn run lint:prettier] => 134,
    %w[bin/rake gettext:lint] => 81,
    %w[bundle exec license_finder] => 49,
    %w[bin/rake lint:static_verification] => 24,
    %w[bin/rake gitlab:sidekiq:all_queues_yml:check] => 12,
    (Gitlab.ee? ? %w[bin/rake gitlab:sidekiq:sidekiq_queues_yml:check] : nil) => 11,
    %w[bin/rake config_lint] => 11,
    %w[yarn run internal:stylelint] => 8,
    %w[scripts/lint-conflicts.sh] => 1,
    %w[yarn run block-dependencies] => 1,
    %w[scripts/lint-rugged] => 1,
    %w[scripts/gemfile_lock_changed.sh] => 1,
    %w[scripts/frontend/check_no_partial_karma_jest.sh] => 1
  }.reject { |k| k.nil? }.sort_by { |a| -a[1] }.to_h.keys.freeze

  def run_tasks!
    tasks = tasks_to_run((ENV['CI_NODE_INDEX'] || 1).to_i, (ENV['CI_NODE_TOTAL'] || 1).to_i)

    static_analysis = Gitlab::Popen::Runner.new

    static_analysis.run(tasks) do |cmd, &run|
      puts
      puts "$ #{cmd.join(' ')}"

      result = run.call

      puts "==> Finished in #{result.duration} seconds"
      puts
    end

    puts
    puts '==================================================='
    puts
    puts

    if static_analysis.all_success_and_clean?
      puts 'All static analyses passed successfully.'
    elsif static_analysis.all_success?
      puts 'All static analyses passed successfully, but we have warnings:'
      puts

      emit_warnings(static_analysis)

      exit 2 if warning_count(static_analysis).nonzero?
    else
      puts 'Some static analyses failed:'

      emit_warnings(static_analysis)
      emit_errors(static_analysis)

      exit 1
    end
  end

  def emit_warnings(static_analysis)
    static_analysis.warned_results.each do |result|
      puts
      puts "**** #{result.cmd.join(' ')} had the following warning(s):"
      puts
      puts result.stderr
      puts
    end
  end

  def emit_errors(static_analysis)
    static_analysis.failed_results.each do |result|
      puts
      puts "**** #{result.cmd.join(' ')} failed with the following error(s):"
      puts
      puts result.stdout
      puts result.stderr
      puts
    end
  end

  def warning_count(static_analysis)
    static_analysis.warned_results
      .count { |result| !ALLOWED_WARNINGS.include?(result.stderr.strip) }
  end

  def tasks_to_run(node_index, node_total)
    tasks = []
    TASKS_BY_DURATIONS_SECONDS_DESC.each_with_index do |task, i|
      tasks << task if i % node_total == (node_index - 1)
    end

    tasks
  end
end

if $0 == __FILE__
  StaticAnalysis.new.run_tasks!
end