Skip to content

Output report paths specified in the configuration file should be relative to that configuration file #3656

Closed
@james-johnston-thumbtack

Description

Welcome

  • Yes, I'm using a binary release within 2 latest major releases. Only such installations are supported.
  • Yes, I've searched similar issues on GitHub and didn't find any.
  • Yes, I've included all information below (version, config, etc.).
  • Yes, I've tried with the standalone linter if available (e.g., gocritic, go vet, etc.). (https://golangci-lint.run/usage/linters/)

Description of the problem

The path for the output report specified in a configuration file is apparently relative to the current working directory that golangci-lint is invoked from, rather than relative to the configuration file itself. This is a problem if the configuration file is located in a parent directory.

Steps to reproduce

  1. Create a configuration file at ~/Thumbtack/go/src/github.com/thumbtack/go/.golangci.yml that includes the following:
output:
  format: colored-line-number,junit-xml:reports/junit/lint.xml
  1. Create a directory at ~/Thumbtack/go/src/github.com/thumbtack/go/reports/junit that will hold the report output. (The directory creation as a first step is necessary because this has not been implemented: Automatically create output directory for reports #3623)
  2. Change directory to a sub-package, and then run the linter:
% cd ~/Thumbtack/go/src/github.com/thumbtack/go/lib/system
% pwd
/Users/james.johnston/Thumbtack/go/src/github.com/thumbtack/go/lib/system
% golangci-lint --verbose run ./...

Expected behavior

The linter should write a report file at ~/Thumbtack/go/src/github.com/thumbtack/go/reports/junit/lint.xml because that is the output path specified in the configuration file (relative to the directory containing the configuration file).

Actual behavior

The linter fails with an error, because it tries to write the output in a location relative to the current working directory (lib/system sub-directory), and does not find a pre-existing output directory there:

INFO [config_reader] Config search paths: [./ /Users/james.johnston/Thumbtack/go/src/github.com/thumbtack/go/lib/system /Users/james.johnston/Thumbtack/go/src/github.com/thumbtack/go/lib /Users/james.johnston/Thumbtack/go/src/github.com/thumbtack/go /Users/james.johnston/Thumbtack/go/src/github.com/thumbtack /Users/james.johnston/Thumbtack/go/src/github.com /Users/james.johnston/Thumbtack/go/src /Users/james.johnston/Thumbtack/go /Users/james.johnston/Thumbtack /Users/james.johnston /Users /]
INFO [config_reader] Used config file ../../.golangci.yml
INFO [lintersdb] Active 16 linters: [bodyclose errcheck errorlint gocritic gosec gosimple govet ineffassign noctx reassign revive scopelint staticcheck stylecheck typecheck unused]
INFO [loader] Go packages loading at mode 575 (imports|types_sizes|compiled_files|deps|files|exports_file|name) took 141.1195ms
WARN [runner] The linter 'scopelint' is deprecated (since v1.39.0) due to: The repository of the linter has been deprecated by the owner.  Replaced by exportloopref.
INFO [runner/filename_unadjuster] Pre-built 0 adjustments in 1.335042ms
INFO [linters_context/goanalysis] analyzers took 0s with no stages
INFO [runner] Issues before processing: 30, after processing: 0
INFO [runner] Processors filtering stat (out/in): uniq_by_line: 21/24, path_prettifier: 30/30, exclude-rules: 24/28, nolint: 24/24, cgo: 30/30, exclude: 28/28, skip_dirs: 30/30, autogenerated_exclude: 28/30, diff: 0/21, filename_unadjuster: 30/30, skip_files: 30/30, identifier_marker: 28/28
INFO [runner] processing took 250.08712ms with stages: diff: 247.169708ms, nolint: 1.074416ms, exclude-rules: 676.333µs, autogenerated_exclude: 481.041µs, identifier_marker: 369.041µs, path_prettifier: 299.292µs, skip_dirs: 8.041µs, uniq_by_line: 3.375µs, cgo: 1.666µs, filename_unadjuster: 1.25µs, max_same_issues: 1.082µs, sort_results: 416ns, skip_files: 250ns, source_code: 250ns, path_shortener: 250ns, max_per_file_from_linter: 209ns, exclude: 209ns, max_from_linter: 125ns, severity-rules: 84ns, path_prefixer: 82ns
INFO [runner] linters took 705.687375ms with stages: goanalysis_metalinter: 455.4205ms
ERRO Running error: can't create output for reports/junit/lint.xml: open reports/junit/lint.xml: no such file or directory
INFO Memory: 10 samples, avg is 43.7MB, max is 57.3MB
INFO Execution took 856.919584ms

In the above logs, note the following:

  • The configuration file used is ../../.golangci.yml relative to the current working directory.
  • However, it tries to write output to reports/junit/lint.xml -- not ../../reports/junit/lint.xml which would be the path relative to the configuration file.

Thoughts on solving this

At the end of the day, all I want to do is write the JUnit output during CI/CD to a subdirectory relative to the base of our repository / relative to the configuration file, while at the same time allow ordinary developers to at least be able to run golangci-lint from any directory without encountering an error.

Whether that is solved by addressing #3623, providing a way to evaluate the output path relative to the configuration file, or some other maintainable solution is relatively inconsequential to me. It is the combination of both of these issues that presents a problem where it's just not practical to put output files in a subdirectory.

Version of golangci-lint

$ golangci-lint --version
golangci-lint has version 1.51.0 built from 6d3f06c5 on 2023-02-02T08:24:52Z

The problem still exists in master branch:

  1. At
    formats := strings.Split(e.cfg.Output.Format, ",")
    for _, format := range formats {
    out := strings.SplitN(format, ":", 2)
    if len(out) < 2 {
    out = append(out, "")
    }
    err := e.printReports(ctx, issues, out[1], out[0])
    if err != nil {
    return err
    }
    }
    we can see that the output path is passed as-is as a parameter to the printReports function.
  2. At
    func (e *Executor) printReports(ctx context.Context, issues []result.Issue, path, format string) error {
    w, shouldClose, err := e.createWriter(path)
    we see that it is then passed to createWriter as-is.
  3. Which at
    func (e *Executor) createWriter(path string) (io.Writer, bool, error) {
    if path == "" || path == "stdout" {
    return logutils.StdOut, false, nil
    }
    if path == "stderr" {
    return logutils.StdErr, false, nil
    }
    f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode)
    we can see it is then passed to os.OpenFile as-is.
  4. At no point is the path being adjusted to compensate for the location where the path was actually specified (e.g. the directory containing the configuration file).

Configuration file

Relevant part cited in problem description, above.

Go environment

% go version && go env
go version go1.19.5 darwin/arm64
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/james.johnston/Library/Caches/go-build"
GOENV="/Users/james.johnston/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/james.johnston/Thumbtack/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/james.johnston/Thumbtack/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.19.5"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/james.johnston/Thumbtack/go/src/github.com/thumbtack/go/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS="-I/usr/local/include"
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-L/usr/local/lib"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/91/w0njycjx075fd3lpnbzl44_h0000gp/T/go-build709362124=/tmp/go-build -gno-record-gcc-switches -fno-common"

Verbose output of running

Relevant verbose output cited in problem description.

Code example or link to a public repository

Enough information should be provided in the problem description to reproduce it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: configRelated to .golangci.yml and/or cli optionsarea: outputRelated to issue outputenhancementNew feature or improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions