Welcome to mirror list, hosted at ThFree Co, Russian Federation.

build.go « testcfg « testhelper « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: a39a409f49f1c970292151f7a7dfc0c87e433331 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package testcfg

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"testing"

	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest"
	"gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config"
	"gitlab.com/gitlab-org/gitaly/v15/internal/testhelper"
)

var buildOnceByName sync.Map

// BuildGitalyGit2Go builds the gitaly-git2go command and installs it into the binary directory.
func BuildGitalyGit2Go(t testing.TB, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-git2go-v15"))
}

// BuildGitalyWrapper builds the gitaly-wrapper command and installs it into the binary directory.
func BuildGitalyWrapper(t *testing.T, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-wrapper"))
}

// BuildGitalyLFSSmudge builds the gitaly-lfs-smudge command and installs it into the binary
// directory.
func BuildGitalyLFSSmudge(t *testing.T, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-lfs-smudge"))
}

// BuildGitalyHooks builds the gitaly-hooks command and installs it into the binary directory.
func BuildGitalyHooks(t testing.TB, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-hooks"))
}

// BuildGitalySSH builds the gitaly-ssh command and installs it into the binary directory.
func BuildGitalySSH(t testing.TB, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-ssh"))
}

// BuildPraefect builds the praefect command and installs it into the binary directory.
func BuildPraefect(t testing.TB, cfg config.Cfg) string {
	return BuildBinary(t, cfg.BinDir, gitalyCommandPath("praefect"))
}

var (
	sharedBinariesDir               string
	createGlobalBinaryDirectoryOnce sync.Once
)

// BuildBinary builds a Go binary once and copies it into the target directory. The source path can
// either be a ".go" file or a directory containing Go files. Returns the path to the executable in
// the destination directory.
func BuildBinary(t testing.TB, targetDir, sourcePath string) string {
	createGlobalBinaryDirectoryOnce.Do(func() {
		sharedBinariesDir = testhelper.CreateGlobalDirectory(t, "bins")
	})
	require.NotEmpty(t, sharedBinariesDir, "creation of shared binary directory failed")

	var (
		// executableName is the name of the executable.
		executableName = filepath.Base(sourcePath)
		// sharedBinaryPath is the path to the binary shared between all tests.
		sharedBinaryPath = filepath.Join(sharedBinariesDir, executableName)
		// targetPath is the final path where the binary should be copied to.
		targetPath = filepath.Join(targetDir, executableName)
	)

	buildOnceInterface, _ := buildOnceByName.LoadOrStore(executableName, &sync.Once{})
	buildOnce, ok := buildOnceInterface.(*sync.Once)
	require.True(t, ok)

	buildOnce.Do(func() {
		require.NoFileExists(t, sharedBinaryPath, "binary has already been built")

		cfg := Build(t)
		gitCommandFactory := gittest.NewCommandFactory(t, cfg)
		gitExecEnv := gitCommandFactory.GetExecutionEnvironment(context.TODO())

		// Unfortunately, Go has started to execute Git as parts of its build process in
		// order to embed VCS information into the resulting binary. In Gitaly we're doing a
		// bunch of things to verify that we don't ever use Git information from outside of
		// our defined parameters: we intercept Git executed via PATH, and we also override
		// Git configuration locations. So executing Git without special logic simply does
		// not work.
		//
		// While we could in theory just ask it not to do that via `-buildvcs=false`, this
		// option is only understood with Go 1.18+. So we have the option between either
		// using logic that is conditional on the Go version here, or alternatively we fix
		// the environment to allow for the execution of Git. We opt for the latter here and
		// set up a Git command factory.
		gitEnvironment := make([]string, 0, len(os.Environ()))

		// We need to filter out some environments we set globally in our tests which would
		// cause Git to not operate correctly.
		for _, env := range os.Environ() {
			shouldExclude := false
			for _, prefix := range []string{
				"GIT_DIR=",
				"GIT_CONFIG_GLOBAL=",
				"GIT_CONFIG_SYSTEM=",
			} {
				if strings.HasPrefix(env, prefix) {
					shouldExclude = true
					break
				}
			}
			if !shouldExclude {
				gitEnvironment = append(gitEnvironment, env)
			}
		}

		// Furthermore, as we're using the Git command factory which may or may not use
		// bundled Git we need to append some environment variables that make Git find its
		// auxiliary helper binaries.
		gitEnvironment = append(gitEnvironment, gitExecEnv.EnvironmentVariables...)

		// And last but not least we need to override PATH so that our Git binary from the
		// command factory is up front.
		gitEnvironment = append(gitEnvironment, fmt.Sprintf(
			"PATH=%s:%s", filepath.Dir(gitExecEnv.BinaryPath), os.Getenv("PATH"),
		))

		cmd := exec.Command(
			"go",
			"build",
			"-tags", "static,system_libgit2",
			"-o", sharedBinaryPath,
			sourcePath,
		)
		cmd.Env = gitEnvironment

		output, err := cmd.CombinedOutput()
		require.NoError(t, err, "building Go executable: %v, output: %q", err, output)
	})

	require.FileExists(t, sharedBinaryPath, "%s does not exist", executableName)
	require.NoFileExists(t, targetPath, "%s exists already -- do you try to build it twice?", executableName)

	require.NoError(t, os.MkdirAll(targetDir, os.ModePerm))

	// We hard-link the file into place instead of copying it because copying used to cause
	// ETXTBSY errors in CI. This is likely caused by a bug in the overlay filesystem used by
	// Docker, so we just work around this by linking the file instead. It's more efficient
	// anyway, the only thing is that no test must modify the binary directly. But let's count
	// on that.
	require.NoError(t, os.Link(sharedBinaryPath, targetPath))

	return targetPath
}

func gitalyCommandPath(command string) string {
	return fmt.Sprintf("gitlab.com/gitlab-org/gitaly/v15/cmd/%s", command)
}