@ 260812.1

240718.1 · d6ebdd1cdd8e059d8c5ebfe409a7f36345a86e44

fix go 1.20 build and retract v1.0.7

This commit fixes the build for Go 1.20 by removing use of the slices package and retracts the v1.0.7 release.

It also changes the CI to test go1.20 (our minimum supported version) so that this will not happen again in the future.

reading declarations

.github/workflows/linux.yml

@@ -11,7 +11,7 @@ jobs:     runs-on: ubuntu-latest     strategy:       matrix:-        go: [1.21, 1.22]+        go: ['1.20', '1.21', '1.22']     steps:     - uses: actions/checkout@v4       with:

Makefile

@@ -47,7 +47,9 @@ test_build_solaris_amd64:  .PHONY: test_build_wasip1_wasm test_build_wasip1_wasm:-	GOOS=wasip1 GOARCH=wasm go test -c -o /dev/null+	@# Ignore versions before 1.21+	go version | grep -qE 'go1\.(20|1[0-9])' || \+		GOOS=wasip1 GOARCH=wasm go test -c -o /dev/null  .PHONY: test_build_aix_ppc64 test_build_aix_ppc64:

dirent_portable.go

@@ -8,9 +8,10 @@ package fastwalk import ( 	"io/fs" 	"os"-	"slices"-	"strings"+	"sort" 	"sync"++	"github.com/charlievieth/fastwalk/internal/fmtdirent" )  var _ DirEntry = (*portableDirent)(nil)@@ -22,7 +23,7 @@ type portableDirent struct { }  func (d *portableDirent) String() string {-	return fs.FormatDirEntry(d)+	return fmtdirent.FormatDirEntry(d) }  func (d *portableDirent) Stat() (fs.FileInfo, error) {@@ -72,52 +73,56 @@ func sortDirents(mode SortMode, dents []DirEntry) { 	} 	switch mode { 	case SortLexical:-		slices.SortFunc(dents, func(d1, d2 DirEntry) int {-			return strings.Compare(d1.Name(), d2.Name())+		sort.Slice(dents, func(i, j int) bool {+			return dents[i].Name() < dents[j].Name() 		}) 	case SortFilesFirst:-		slices.SortFunc(dents, func(d1, d2 DirEntry) int {+		sort.Slice(dents, func(i, j int) bool {+			d1 := dents[i]+			d2 := dents[j] 			r1 := d1.Type().IsRegular() 			r2 := d2.Type().IsRegular() 			switch { 			case r1 && !r2:-				return -1+				return true 			case !r1 && r2:-				return 1+				return false 			case !r1 && !r2: 				// Both are not regular files: sort directories last 				dd1 := d1.Type().IsDir() 				dd2 := d2.Type().IsDir() 				switch { 				case !dd1 && dd2:-					return -1+					return true 				case dd1 && !dd2:-					return 1+					return false 				} 			}-			return strings.Compare(d1.Name(), d2.Name())+			return d1.Name() < d2.Name() 		}) 	case SortDirsFirst:-		slices.SortFunc(dents, func(d1, d2 DirEntry) int {+		sort.Slice(dents, func(i, j int) bool {+			d1 := dents[i]+			d2 := dents[j] 			dd1 := d1.Type().IsDir() 			dd2 := d2.Type().IsDir() 			switch { 			case dd1 && !dd2:-				return -1+				return true 			case !dd1 && dd2:-				return 1+				return false 			case !dd1 && !dd2: 				// Both are not directories: sort regular files first 				r1 := d1.Type().IsRegular() 				r2 := d2.Type().IsRegular() 				switch { 				case r1 && !r2:-					return -1+					return true 				case !r1 && r2:-					return 1+					return false 				} 			}-			return strings.Compare(d1.Name(), d2.Name())+			return d1.Name() < d2.Name() 		}) 	} }

dirent_portable_test.go

@@ -8,6 +8,8 @@ import ( 	"reflect" 	"testing" 	"time"++	"github.com/charlievieth/fastwalk/internal/fmtdirent" )  var _ DirEntry = dirEntry{}@@ -25,7 +27,7 @@ func (de dirEntry) Info() (fs.FileInfo, error) { panic("not implemented") } func (de dirEntry) Stat() (fs.FileInfo, error) { panic("not implemented") }  func (de dirEntry) String() string {-	return fs.FormatDirEntry(de)+	return fmtdirent.FormatDirEntry(de) }  // NB: this must be kept in sync with the

dirent_unix.go

@@ -5,9 +5,10 @@ package fastwalk import ( 	"io/fs" 	"os"-	"slices"-	"strings"+	"sort" 	"sync"++	"github.com/charlievieth/fastwalk/internal/fmtdirent" )  type unixDirent struct {@@ -21,7 +22,7 @@ type unixDirent struct { func (d *unixDirent) Name() string      { return d.name } func (d *unixDirent) IsDir() bool       { return d.typ.IsDir() } func (d *unixDirent) Type() fs.FileMode { return d.typ }-func (d *unixDirent) String() string    { return fs.FormatDirEntry(d) }+func (d *unixDirent) String() string    { return fmtdirent.FormatDirEntry(d) }  func (d *unixDirent) Info() (fs.FileInfo, error) { 	info := loadFileInfo(&d.info)@@ -87,52 +88,56 @@ func sortDirents(mode SortMode, dents []*unixDirent) { 	} 	switch mode { 	case SortLexical:-		slices.SortFunc(dents, func(d1, d2 *unixDirent) int {-			return strings.Compare(d1.name, d2.name)+		sort.Slice(dents, func(i, j int) bool {+			return dents[i].name < dents[j].name 		}) 	case SortFilesFirst:-		slices.SortFunc(dents, func(d1, d2 *unixDirent) int {+		sort.Slice(dents, func(i, j int) bool {+			d1 := dents[i]+			d2 := dents[j] 			r1 := d1.typ.IsRegular() 			r2 := d2.typ.IsRegular() 			switch { 			case r1 && !r2:-				return -1+				return true 			case !r1 && r2:-				return 1+				return false 			case !r1 && !r2: 				// Both are not regular files: sort directories last 				dd1 := d1.typ.IsDir() 				dd2 := d2.typ.IsDir() 				switch { 				case !dd1 && dd2:-					return -1+					return true 				case dd1 && !dd2:-					return 1+					return false 				} 			}-			return strings.Compare(d1.name, d2.name)+			return d1.name < d2.name 		}) 	case SortDirsFirst:-		slices.SortFunc(dents, func(d1, d2 *unixDirent) int {+		sort.Slice(dents, func(i, j int) bool {+			d1 := dents[i]+			d2 := dents[j] 			dd1 := d1.typ.IsDir() 			dd2 := d2.typ.IsDir() 			switch { 			case dd1 && !dd2:-				return -1+				return true 			case !dd1 && dd2:-				return 1+				return false 			case !dd1 && !dd2: 				// Both are not directories: sort regular files first 				r1 := d1.typ.IsRegular() 				r2 := d2.typ.IsRegular() 				switch { 				case r1 && !r2:-					return -1+					return true 				case !r1 && r2:-					return 1+					return false 				} 			}-			return strings.Compare(d1.name, d2.name)+			return d1.name < d2.name 		}) 	} }

go.mod

@@ -1,3 +1,5 @@ module github.com/charlievieth/fastwalk  go 1.20++retract v1.0.7 // Build broken on Go 1.20

internal/fmtdirent/fmtdirent_go120.go

new file mode 100644@@ -0,0 +1,31 @@+//go:build !go1.21++package fmtdirent++import "io/fs"++// Backport fs.FormatDirEntry from go1.21++// FormatDirEntry returns a formatted version of dir for human readability.+// Implementations of [DirEntry] can call this from a String method.+// The outputs for a directory named subdir and a file named hello.go are:+//+//	d subdir/+//	- hello.go+func FormatDirEntry(dir fs.DirEntry) string {+	name := dir.Name()+	b := make([]byte, 0, 5+len(name))++	// The Type method does not return any permission bits,+	// so strip them from the string.+	mode := dir.Type().String()+	mode = mode[:len(mode)-9]++	b = append(b, mode...)+	b = append(b, ' ')+	b = append(b, name...)+	if dir.IsDir() {+		b = append(b, '/')+	}+	return string(b)+}

internal/fmtdirent/fmtdirent_go120_test.go

new file mode 100644@@ -0,0 +1,118 @@+//go:build !go1.21++// Copyright 2023 The Go Authors. All rights reserved.+// Use of this source code is governed by a BSD-style+// license that can be found in the LICENSE file.++// Backport fs.FormatDirEntry tests from go1.21. We don't test+// the go1.21+ FormatDirEntry function since it just calls the+// stdlib and we don't want changes in its output to break our+// tests.++package fmtdirent_test++import (+	. "io/fs"+	"testing"+	"time"++	"github.com/charlievieth/fastwalk/internal/fmtdirent"+)++// formatTest implements FileInfo to test FormatFileInfo,+// and implements DirEntry to test FormatDirEntry.+type formatTest struct {+	name    string+	size    int64+	mode    FileMode+	modTime time.Time+	isDir   bool+}++func (fs *formatTest) Name() string {+	return fs.name+}++func (fs *formatTest) Size() int64 {+	return fs.size+}++func (fs *formatTest) Mode() FileMode {+	return fs.mode+}++func (fs *formatTest) ModTime() time.Time {+	return fs.modTime+}++func (fs *formatTest) IsDir() bool {+	return fs.isDir+}++func (fs *formatTest) Sys() any {+	return nil+}++func (fs *formatTest) Type() FileMode {+	return fs.mode.Type()+}++func (fs *formatTest) Info() (FileInfo, error) {+	return fs, nil+}++var formatTests = []struct {+	input        formatTest+	wantDirEntry string+}{+	{+		formatTest{+			name:    "hello.go",+			size:    100,+			mode:    0o644,+			modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),+			isDir:   false,+		},+		"- hello.go",+	},+	{+		formatTest{+			name:    "home/gopher",+			size:    0,+			mode:    ModeDir | 0o755,+			modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),+			isDir:   true,+		},+		"d home/gopher/",+	},+	{+		formatTest{+			name:    "big",+			size:    0x7fffffffffffffff,+			mode:    ModeIrregular | 0o644,+			modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),+			isDir:   false,+		},+		"? big",+	},+	{+		formatTest{+			name:    "small",+			size:    -0x8000000000000000,+			mode:    ModeSocket | ModeSetuid | 0o644,+			modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),+			isDir:   false,+		},+		"S small",+	},+}++func TestFormatDirEntry(t *testing.T) {+	for i, test := range formatTests {+		got := fmtdirent.FormatDirEntry(&test.input)+		if got != test.wantDirEntry {+			t.Errorf("%d: FormatDirEntry(%#v) = %q, want %q", i, test.input, got, test.wantDirEntry)+		}+	}++}

internal/fmtdirent/fmtdirent_go121.go

new file mode 100644@@ -0,0 +1,15 @@+//go:build go1.21++package fmtdirent++import "io/fs"++// FormatDirEntry returns a formatted version of dir for human readability.+// Implementations of [DirEntry] can call this from a String method.+// The outputs for a directory named subdir and a file named hello.go are:+//+//	d subdir/+//	- hello.go+func FormatDirEntry(dir fs.DirEntry) string {+	return fs.FormatDirEntry(dir)+}