diff --git a/lib/license_finder/package_managers/go_modules.rb b/lib/license_finder/package_managers/go_modules.rb index 897b9acd7..3d2b94685 100644 --- a/lib/license_finder/package_managers/go_modules.rb +++ b/lib/license_finder/package_managers/go_modules.rb @@ -30,12 +30,30 @@ def current_packages def packages_info Dir.chdir(project_path) do - info_output, stderr, _status = Cmd.run("GO111MODULE=on go list -m -f '{{.Path}},{{.Version}},{{.Dir}}' all") - if stderr =~ Regexp.compile("can't compute 'all' using the vendor directory") - info_output, _stderr, _status = Cmd.run("GO111MODULE=on go list -m -mod=mod -f '{{.Path}},{{.Version}},{{.Dir}}' all") - end + # Explanations: + # * Ignore standard library packages + # (not .Standard) + # * Replacement modules are respected + # (or .Module.Replace .Module) + # * Module cache directory or (vendored) package directory + # (or $mod.Dir .Dir) + format_str = \ + '{{ if not .Standard }}'\ + '{{ $mod := (or .Module.Replace .Module) }}'\ + '{{ $mod.Path }},{{ $mod.Version }},{{ or $mod.Dir .Dir }}'\ + '{{ end }}' - info_output.split("\n") + # The module list flag (`-m`) is intentionally not used here. If the module + # dependency tree were followed, transitive dependencies that are never imported + # may be included. + # + # Instead, the owning module is listed for each imported package. This better + # matches the implementation of other Go package managers. + info_output, stderr, _status = Cmd.run("GO111MODULE=on go list -f '#{format_str}' all") + info_output, _stderr, _status = Cmd.run("GO111MODULE=on go list -mod=mod -f '#{format_str}' all") if stderr =~ Regexp.compile("can't compute 'all' using the vendor directory") + + # Since many packages may belong to a single module, #uniq is used to deduplicate + info_output.split("\n").uniq end end diff --git a/spec/lib/license_finder/package_managers/go_modules_spec.rb b/spec/lib/license_finder/package_managers/go_modules_spec.rb index e94e7e3f7..a7fa0d65d 100644 --- a/spec/lib/license_finder/package_managers/go_modules_spec.rb +++ b/spec/lib/license_finder/package_managers/go_modules_spec.rb @@ -10,9 +10,11 @@ module LicenseFinder let(:src_path) { '/workspace/code' } let(:mod_path) { "#{src_path}/go.mod" } let(:vendor_path) { "#{src_path}/vendor" } + let(:go_list_format) { '{{ if not .Standard }}{{ $mod := (or .Module.Replace .Module) }}{{ $mod.Path }},{{ $mod.Version }},{{ or $mod.Dir .Dir }}{{ end }}' } let(:go_list_string) do "foo,,/workspace/code/\ngopkg.in/check.v1,v0.0.0-20161208181325-20d25e280405,"\ "/workspace/LicenseFinder/features/fixtures/go_modules/vendor/gopkg.in/check.v1\n"\ +"gopkg.in/yaml.v2,v2.2.1,/workspace/LicenseFinder/features/fixtures/go_modules/vendor/gopkg.in/yaml.v2\n"\ 'gopkg.in/yaml.v2,v2.2.1,/workspace/LicenseFinder/features/fixtures/go_modules/vendor/gopkg.in/yaml.v2' end subject { GoModules.new(project_path: Pathname(src_path), logger: double(:logger, active: nil)) } @@ -24,7 +26,7 @@ module LicenseFinder FileUtils.mkdir_p(vendor_path) File.write(mod_path, content) - allow(SharedHelpers::Cmd).to receive(:run).with("GO111MODULE=on go list -m -f '{{.Path}},{{.Version}},{{.Dir}}' all").and_return(go_list_string) + allow(SharedHelpers::Cmd).to receive(:run).with("GO111MODULE=on go list -f '#{go_list_format}' all").and_return(go_list_string) end after do @@ -58,10 +60,10 @@ module LicenseFinder context 'when compute is not allowed on vendor' do before do allow(SharedHelpers::Cmd).to receive(:run) - .with("GO111MODULE=on go list -m -f '{{.Path}},{{.Version}},{{.Dir}}' all") + .with("GO111MODULE=on go list -f '#{go_list_format}' all") .and_return(['', "go list -m: can't compute 'all' using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)\n", 1]) allow(SharedHelpers::Cmd).to receive(:run) - .with("GO111MODULE=on go list -m -mod=mod -f '{{.Path}},{{.Version}},{{.Dir}}' all") + .with("GO111MODULE=on go list -mod=mod -f '#{go_list_format}' all") .and_return(go_list_string) end