Skip to content

x/mod/modfile: AddNewRequire doesn't put direct dependencies in the first block #69050

Open
golang/mod
#21
@stevenh

Description

@stevenh

Go version

go version go1.23.0 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/steveh/.cache/go-build'
GOENV='/home/steveh/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/steveh/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/steveh/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/steveh/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/steveh/code/github.com/rocketsciencegg/congestion-control/tools/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build155249492=/tmp/go-build -gno-record-gcc-switches'

What did you do?

Use AddNewRequire to add new requires.

package main

import (
	"testing"

	"github.com/stretchr/testify/require"
	"golang.org/x/mod/modfile"
	"golang.org/x/mod/module"
)

var testFile = `module test

go 1.23.0

require (
	github.com/test/test1 v0.1.0
	github.com/test/test2 v0.1.0
)

require (
	github.com/test/test3 v0.1.0 // indirect
)
`

var expected = `module test

go 1.23.0

require (
	github.com/foo/direct1 v0.1.1
	github.com/foo/direct2 v0.1.2
	github.com/test/test1 v0.1.0
	github.com/test/test2 v0.1.0
)

require (
	github.com/foo/indirect1 v0.2.1 // indirect
	github.com/foo/indirect2 v0.2.2 // indirect
	github.com/test/test3 v0.1.0 // indirect
)
`

func TestAddRequire(t *testing.T) {
	file, err := modfile.Parse("go.mod", []byte(testFile), nil)
	require.NoError(t, err)

	file.AddNewRequire("github.com/foo/indirect2", "v0.2.2", true)
	file.AddNewRequire("github.com/foo/direct2", "v0.1.2", false)
	file.AddNewRequire("github.com/foo/direct1", "v0.1.1", false)
	file.AddNewRequire("github.com/foo/indirect1", "v0.2.1", true)

	file.Cleanup()
	file.SortBlocks()

	data, err := file.Format()
	require.NoError(t, err)
	require.Equal(t, expected, string(data))
}

What did you see happen?

Direct requires should be added to first require block, indirect requires should be added to second block.

What did you expect to see?

When using AddNewRequire, requires are added to the last block. This is the documented behaviour, but other go tools such as go mod tidy maintain two blocks, so this use the same approach.

It seems possible to use SetRequireSeparateIndirect to replicate the desired behaviour but that was far from obvious.

If nothing else reference in AddNewRequire and Addequire to SetRequireSeparateIndirect would help users fine the right functionality, but ideally AddNewRequire and AddRequire should function as expected.

If a user isn't aware of the two block rule then using this will result in a file that go mod tidy handles badly, in some cases resulting in three blocks instead of two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.modules

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions