summaryrefslogtreecommitdiff
path: root/lib/license/finder/ext/go_modules.rb
blob: 8a9ea031b94bc07455420a5e090678d2aa9432bb (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
# frozen_string_literal: true

module LicenseFinder
  class GoModules
    FORMAT = "'{{.Main}},{{.Path}},{{.Version}},{{.Dir}}'"
    HEADER = [:main_module, :name, :version, :dir].freeze

    def active?
      return if project_path.to_path.include?('/vendor/')

      go_sum_path.exist?
    end

    def prepare
      return if vendored?

      within_project_path do
        tool_box.install(tool: :golang)
        shell.execute([:go, :mod, :download, '-json'], capture: false)
      end
    end

    def current_packages
      within_project_path do
        modules = vendored? ? parse_go_sum : go_list_all
        modules.map { |hash| map_from(hash) }.compact
      end
    end

    private

    def go_list_all
      env = { 'GOFLAGS' => ENV.fetch('GOFLAGS', '-mod=readonly') }
      command = [:go, :list, '-m', '-f', FORMAT, :all]
      stdout, _stderr, status = shell.execute(command, env: env)
      return [] unless status.success?

      stdout.each_line.map { |line| Hash[HEADER.zip(line.chomp.split(','))] }
    end

    def parse_go_sum
      go_sum_path
        .each_line.map { |x| x.split(' ') }
        .each_with_object({}) do |(name, version), memo|
          next unless module_path?(name)

          memo["#{name}:#{version}"] = {
            name: name,
            version: version.split('/')[0],
            dir: vendored_path_to(name)
          }
        end.values
    end

    def map_from(hash)
      return if hash[:main_module] == "true"

      Dependency.new(
        'Go',
        hash[:name],
        hash[:version],
        install_path: install_dir_for(hash),
        detection_path: go_sum_path
      )
    end

    def go_sum_path
      @go_sum_path ||= project_path.join('go.sum')
    end

    def vendor_path
      @vendor_path ||= go_sum_path.parent.join('vendor')
    end

    def vendored?
      vendor_path.exist? && vendor_path.directory?
    end

    def vendored_path_to(module_name)
      vendor_path.join(module_name)
    end

    def install_dir_for(hash)
      dir = hash[:dir]
      pathname = dir && !dir.empty? ? Pathname.new(dir) : vendored_path_to(hash[:name])
      pathname.exist? ? pathname : nil
    end

    # https://golang.org/ref/mod#tmp_9
    def module_path?(module_path)
      !module_path.start_with?('/') &&
        !module_path.end_with?('/') &&
        module_path.split('/').all? { |x| element?(x) }
    end

    def element?(element)
      !element.empty? &&
        !element.start_with?('.') &&
        !element.end_with?('.') &&
        element.match?(/\A[A-Za-z0-9+-._~]+\Z/)
    end
  end
end