fix: adjust build defaults when tmp_dir is customized (#888)
* fix: adjust build defaults when tmp_dir is customized When a user sets tmp_dir to a non-default value (e.g. ".tmp"), Build.Cmd, Build.Bin, and Build.ExcludeDir still referenced the default "tmp" directory, causing the build to create a "tmp/" folder alongside the intended custom directory. Add adjustDefaultsForTmpDir() to update these fields when they still hold their default values and TmpDir has been changed. Explicitly set values are preserved. Fixes #780 Signed-off-by: majiayu000 <1835304752@qq.com> * test: add coverage for Windows branch in adjustDefaultsForTmpDir Extract OS parameter to allow testing the Windows code path on any platform, covering the lines flagged by Codecov. Signed-off-by: majiayu000 <1835304752@qq.com> * fix: support absolute tmp_dir defaults and preserve custom exclude_dir * fix: make tmp_dir absolute path handling OS-agnostic --------- Signed-off-by: majiayu000 <1835304752@qq.com> Co-authored-by: xiantang <zhujingdi1998@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -392,6 +393,7 @@ func (c *Config) preprocess(args map[string]TomlInfo) error {
|
||||
if c.TestDataDir == "" {
|
||||
c.TestDataDir = "testdata"
|
||||
}
|
||||
c.adjustDefaultsForTmpDir()
|
||||
ed := c.Build.ExcludeDir
|
||||
for i := range ed {
|
||||
ed[i] = cleanPath(ed[i])
|
||||
@@ -446,6 +448,92 @@ func (c *Config) preprocess(args map[string]TomlInfo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// adjustDefaultsForTmpDir updates Build.Cmd, Build.Bin, and Build.ExcludeDir
|
||||
// when they still hold their default values but TmpDir has been changed.
|
||||
func (c *Config) adjustDefaultsForTmpDir() {
|
||||
c.adjustDefaultsForTmpDirWithOS(runtime.GOOS)
|
||||
}
|
||||
|
||||
func (c *Config) adjustDefaultsForTmpDirWithOS(goos string) {
|
||||
const defaultTmpDir = "tmp"
|
||||
if c.TmpDir == defaultTmpDir {
|
||||
return
|
||||
}
|
||||
|
||||
defaultCmd := "go build -o ./tmp/main ."
|
||||
defaultBin := "./tmp/main"
|
||||
mainBinary := "main"
|
||||
if goos == PlatformWindows {
|
||||
defaultCmd = "go build -o ./tmp/main.exe ."
|
||||
defaultBin = `tmp\main.exe`
|
||||
mainBinary = "main.exe"
|
||||
}
|
||||
|
||||
newBinPath := filepath.Join(c.TmpDir, mainBinary)
|
||||
normalizedBinPath := strings.ReplaceAll(newBinPath, `\`, "/")
|
||||
newBin := "./" + normalizedBinPath
|
||||
cmdOut := newBin
|
||||
if isAbsPathForOS(goos, c.TmpDir) {
|
||||
newBin = newBinPath
|
||||
cmdOut = normalizedBinPath
|
||||
}
|
||||
if goos == PlatformWindows {
|
||||
if isAbsPathForOS(goos, c.TmpDir) {
|
||||
newBin = strings.ReplaceAll(newBinPath, "/", "\\")
|
||||
cmdOut = normalizedBinPath
|
||||
} else {
|
||||
newBin = strings.ReplaceAll(newBinPath, "/", "\\")
|
||||
cmdOut = "./" + normalizedBinPath
|
||||
}
|
||||
}
|
||||
newCmd := "go build -o " + cmdOut + " ."
|
||||
|
||||
if c.Build.Cmd == defaultCmd {
|
||||
c.Build.Cmd = newCmd
|
||||
}
|
||||
if c.Build.Bin == defaultBin {
|
||||
c.Build.Bin = newBin
|
||||
}
|
||||
if isDefaultExcludeDir(c.Build.ExcludeDir) {
|
||||
for i, dir := range c.Build.ExcludeDir {
|
||||
if dir == defaultTmpDir {
|
||||
c.Build.ExcludeDir[i] = c.TmpDir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultExcludeDir(dirs []string) bool {
|
||||
defaultDirs := []string{"assets", "tmp", "vendor", "testdata"}
|
||||
if len(dirs) != len(defaultDirs) {
|
||||
return false
|
||||
}
|
||||
for i := range dirs {
|
||||
if dirs[i] != defaultDirs[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isAbsPathForOS(goos, path string) bool {
|
||||
if goos != PlatformWindows {
|
||||
return pathpkg.IsAbs(path)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, `\\`) {
|
||||
return true
|
||||
}
|
||||
if len(path) < 3 {
|
||||
return false
|
||||
}
|
||||
drive := path[0]
|
||||
if ((drive >= 'a' && drive <= 'z') || (drive >= 'A' && drive <= 'Z')) && path[1] == ':' {
|
||||
return path[2] == '\\' || path[2] == '/'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Config) colorInfo() map[string]string {
|
||||
return map[string]string{
|
||||
"main": c.Color.Main,
|
||||
|
||||
@@ -360,6 +360,201 @@ cmd = "go build -o ./tmp/main ."
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirAdjustsDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, ".air.toml")
|
||||
cfgContent := `tmp_dir = ".tmp"
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := InitConfig(cfgPath, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("InitConfig error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(cfg.Build.Cmd, ".tmp") {
|
||||
t.Fatalf("expected Build.Cmd to reference .tmp, got %s", cfg.Build.Cmd)
|
||||
}
|
||||
if strings.Contains(cfg.Build.Cmd, "./tmp/") {
|
||||
t.Fatalf("expected Build.Cmd to not reference ./tmp/, got %s", cfg.Build.Cmd)
|
||||
}
|
||||
|
||||
binBase := filepath.Base(cfg.Build.Bin)
|
||||
if runtime.GOOS == "windows" {
|
||||
if binBase != "main.exe" {
|
||||
t.Fatalf("unexpected bin base: %s", binBase)
|
||||
}
|
||||
} else {
|
||||
if binBase != "main" {
|
||||
t.Fatalf("unexpected bin base: %s", binBase)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(cfg.Build.Bin, ".tmp") {
|
||||
t.Fatalf("expected Build.Bin to reference .tmp, got %s", cfg.Build.Bin)
|
||||
}
|
||||
|
||||
foundTmpInExclude := false
|
||||
foundDotTmpInExclude := false
|
||||
for _, dir := range cfg.Build.ExcludeDir {
|
||||
if dir == "tmp" {
|
||||
foundTmpInExclude = true
|
||||
}
|
||||
if dir == ".tmp" {
|
||||
foundDotTmpInExclude = true
|
||||
}
|
||||
}
|
||||
if foundTmpInExclude {
|
||||
t.Fatal("expected ExcludeDir to not contain 'tmp'")
|
||||
}
|
||||
if !foundDotTmpInExclude {
|
||||
t.Fatal("expected ExcludeDir to contain '.tmp'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirAdjustsDefaultsWindows(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := &Config{
|
||||
TmpDir: ".tmp",
|
||||
Build: cfgBuild{
|
||||
Cmd: "go build -o ./tmp/main.exe .",
|
||||
Bin: `tmp\main.exe`,
|
||||
ExcludeDir: []string{"assets", "tmp", "vendor", "testdata"},
|
||||
},
|
||||
}
|
||||
|
||||
cfg.adjustDefaultsForTmpDirWithOS("windows")
|
||||
|
||||
expectedCmd := "go build -o ./.tmp/main.exe ."
|
||||
if cfg.Build.Cmd != expectedCmd {
|
||||
t.Fatalf("expected Build.Cmd %q, got %q", expectedCmd, cfg.Build.Cmd)
|
||||
}
|
||||
expectedBin := `.tmp\main.exe`
|
||||
if cfg.Build.Bin != expectedBin {
|
||||
t.Fatalf("expected Build.Bin %q, got %q", expectedBin, cfg.Build.Bin)
|
||||
}
|
||||
foundDotTmp := false
|
||||
for _, dir := range cfg.Build.ExcludeDir {
|
||||
if dir == "tmp" {
|
||||
t.Fatal("expected ExcludeDir to not contain 'tmp'")
|
||||
}
|
||||
if dir == ".tmp" {
|
||||
foundDotTmp = true
|
||||
}
|
||||
}
|
||||
if !foundDotTmp {
|
||||
t.Fatal("expected ExcludeDir to contain '.tmp'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirDoesNotOverrideExplicitCmd(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, ".air.toml")
|
||||
cfgContent := `tmp_dir = ".tmp"
|
||||
|
||||
[build]
|
||||
cmd = "make build"
|
||||
bin = "./bin/myapp"
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := InitConfig(cfgPath, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("InitConfig error: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Build.Cmd != "make build" {
|
||||
t.Fatalf("expected Build.Cmd to remain 'make build', got %s", cfg.Build.Cmd)
|
||||
}
|
||||
if !strings.Contains(cfg.Build.Bin, "myapp") {
|
||||
t.Fatalf("expected Build.Bin to contain 'myapp', got %s", cfg.Build.Bin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirAdjustsDefaultsWithAbsolutePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
if runtime.GOOS == PlatformWindows {
|
||||
t.Skip("POSIX absolute path test only runs on linux/macos")
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
TmpDir: "/tmp/air-build",
|
||||
Build: cfgBuild{
|
||||
Cmd: "go build -o ./tmp/main .",
|
||||
Bin: "./tmp/main",
|
||||
ExcludeDir: []string{"assets", "tmp", "vendor", "testdata"},
|
||||
},
|
||||
}
|
||||
|
||||
cfg.adjustDefaultsForTmpDirWithOS("linux")
|
||||
|
||||
expectedCmd := "go build -o /tmp/air-build/main ."
|
||||
if cfg.Build.Cmd != expectedCmd {
|
||||
t.Fatalf("expected Build.Cmd %q, got %q", expectedCmd, cfg.Build.Cmd)
|
||||
}
|
||||
expectedBin := "/tmp/air-build/main"
|
||||
if cfg.Build.Bin != expectedBin {
|
||||
t.Fatalf("expected Build.Bin %q, got %q", expectedBin, cfg.Build.Bin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirAdjustsDefaultsWithWindowsAbsolutePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
if runtime.GOOS != PlatformWindows {
|
||||
t.Skip("Windows absolute path test only runs on windows")
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
TmpDir: `C:\tmp\air-build`,
|
||||
Build: cfgBuild{
|
||||
Cmd: "go build -o ./tmp/main.exe .",
|
||||
Bin: `tmp\main.exe`,
|
||||
ExcludeDir: []string{"assets", "tmp", "vendor", "testdata"},
|
||||
},
|
||||
}
|
||||
|
||||
cfg.adjustDefaultsForTmpDirWithOS("windows")
|
||||
|
||||
expectedCmd := "go build -o C:/tmp/air-build/main.exe ."
|
||||
if cfg.Build.Cmd != expectedCmd {
|
||||
t.Fatalf("expected Build.Cmd %q, got %q", expectedCmd, cfg.Build.Cmd)
|
||||
}
|
||||
expectedBin := `C:\tmp\air-build\main.exe`
|
||||
if cfg.Build.Bin != expectedBin {
|
||||
t.Fatalf("expected Build.Bin %q, got %q", expectedBin, cfg.Build.Bin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmpDirDoesNotOverrideExplicitExcludeDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
if runtime.GOOS == PlatformWindows {
|
||||
t.Skip("POSIX absolute path test only runs on linux/macos")
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
TmpDir: ".tmp",
|
||||
Build: cfgBuild{
|
||||
Cmd: "go build -o ./tmp/main .",
|
||||
Bin: "./tmp/main",
|
||||
ExcludeDir: []string{"tmp", "node_modules"},
|
||||
},
|
||||
}
|
||||
|
||||
cfg.adjustDefaultsForTmpDirWithOS("linux")
|
||||
|
||||
if cfg.Build.ExcludeDir[0] != "tmp" {
|
||||
t.Fatalf("expected first exclude_dir value to stay 'tmp', got %q", cfg.Build.ExcludeDir[0])
|
||||
}
|
||||
if cfg.Build.ExcludeDir[1] != "node_modules" {
|
||||
t.Fatalf("expected second exclude_dir value to stay 'node_modules', got %q", cfg.Build.ExcludeDir[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarnIgnoreDangerousRootDirProtection(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("root dir protection uses Unix root path")
|
||||
|
||||
Reference in New Issue
Block a user