Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef56eeeae0 |
@@ -61,6 +61,3 @@
|
|||||||
|
|
||||||
# This repo's own host-built binary (make build).
|
# This repo's own host-built binary (make build).
|
||||||
/bin/mfer
|
/bin/mfer
|
||||||
|
|
||||||
# The protoc script/bootstrap unpacks for script/generate.
|
|
||||||
/bin/protoc
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/bin/mfer
|
/bin/mfer
|
||||||
/bin/protoc/
|
|
||||||
/tmp
|
/tmp
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ COPY . .
|
|||||||
|
|
||||||
# Go half of fmt-check only: this image has no node, so no prettier. The
|
# Go half of fmt-check only: this image has no node, so no prettier. The
|
||||||
# markdown half runs in the mdfmt stage below. The image has no gofumpt
|
# markdown half runs in the mdfmt stage below. The image has no gofumpt
|
||||||
# either; script/gofumpt builds the version bin/tools/go.mod pins.
|
# either; script/gofumpt builds the version tools/go.mod requires.
|
||||||
RUN script/gofumpt --check
|
RUN script/gofumpt --check
|
||||||
# The linter directly, not `make lint`: script/lint builds this stage, and
|
# The linter directly, not `make lint`: script/lint builds this stage, and
|
||||||
# there is no docker inside this build.
|
# there is no docker inside this build.
|
||||||
@@ -18,7 +18,7 @@ RUN golangci-lint run --config .golangci.yml ./...
|
|||||||
|
|
||||||
# Markdown/JSON format stage — prettier needs node, which the Go images
|
# Markdown/JSON format stage — prettier needs node, which the Go images
|
||||||
# do not have. node:22.17.0-bookworm-slim (2026-08-09); ships node
|
# do not have. node:22.17.0-bookworm-slim (2026-08-09); ships node
|
||||||
# 22.17.0 and yarn 1.22.22, the versions .nvmrc and script/bootstrap pin.
|
# 22.17.0 and yarn 1.22.22, the versions script/bootstrap pins.
|
||||||
FROM node@sha256:b04ce4ae4e95b522112c2e5c52f781471a5cbc3b594527bcddedee9bc48c03a0 AS mdfmt
|
FROM node@sha256:b04ce4ae4e95b522112c2e5c52f781471a5cbc3b594527bcddedee9bc48c03a0 AS mdfmt
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|||||||
@@ -67,13 +67,9 @@ standard: normalized scripts in `script/` are the entrypoints for the
|
|||||||
development workflow, and the Makefile targets are thin shims that call them. We
|
development workflow, and the Makefile targets are thin shims that call them. We
|
||||||
provide:
|
provide:
|
||||||
|
|
||||||
- `script/bootstrap` — install all dependencies, idempotently: Go and the
|
- `script/bootstrap` — install all dependencies (Go, Go module download, and
|
||||||
modules of both `go.mod` and `bin/tools/go.mod`; node (the version `.nvmrc`
|
node/yarn plus the prettier version pinned in `package.json`/`yarn.lock`),
|
||||||
names, through nvm when there is no node on `PATH`) and yarn, plus the
|
idempotently; golangci-lint is not installed, it runs only in Docker
|
||||||
prettier version pinned in `package.json`/`yarn.lock`; and `protoc` 33.4,
|
|
||||||
unpacked into `bin/protoc` from its release archive once the archive matches
|
|
||||||
the sha256 the script holds for this platform. golangci-lint is not installed,
|
|
||||||
it runs only in Docker
|
|
||||||
- `script/setup` — make a fresh clone ready for development: runs
|
- `script/setup` — make a fresh clone ready for development: runs
|
||||||
`script/bootstrap`, then `script/install-precommit`
|
`script/bootstrap`, then `script/install-precommit`
|
||||||
- `script/projectname` — output the project name (`mfer`); used by other scripts
|
- `script/projectname` — output the project name (`mfer`); used by other scripts
|
||||||
@@ -86,10 +82,14 @@ provide:
|
|||||||
- `script/generate` (`make generate`) — regenerate `mfer/mf.pb.go` from
|
- `script/generate` (`make generate`) — regenerate `mfer/mf.pb.go` from
|
||||||
`mfer/mf.proto` and record the hash of that `mfer/mf.proto` in
|
`mfer/mf.proto` and record the hash of that `mfer/mf.proto` in
|
||||||
`mfer/mf.proto.sha256`; the only thing that regenerates the committed
|
`mfer/mf.proto.sha256`; the only thing that regenerates the committed
|
||||||
`mfer/mf.pb.go`. It runs the `protoc` that `script/bootstrap` unpacks into
|
`mfer/mf.pb.go`. It needs the exact versions that wrote the committed file,
|
||||||
`bin/protoc`, refusing any version but 33.4, and the `protoc-gen-go` that
|
and refuses to run with any other: `protoc` 33.4 (unpack
|
||||||
`bin/tools/go.mod` pins, which `go tool` builds from source checked against
|
`protoc-33.4-<platform>.zip` from
|
||||||
the hashes in `bin/tools/go.sum`
|
[its release](https://github.com/protocolbuffers/protobuf/releases/tag/v33.4)
|
||||||
|
and put its `bin/protoc` on `PATH`) and `protoc-gen-go` v1.36.11
|
||||||
|
(`go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11`, which
|
||||||
|
installs it in `$(go env GOPATH)/bin`; `script/generate` adds that directory
|
||||||
|
to `PATH`)
|
||||||
- `script/fuzz` — fuzz the manifest parser for one minute; run by hand
|
- `script/fuzz` — fuzz the manifest parser for one minute; run by hand
|
||||||
(`make fuzz`), never by CI, while `script/test` runs its committed seed corpus
|
(`make fuzz`), never by CI, while `script/test` runs its committed seed corpus
|
||||||
as ordinary tests
|
as ordinary tests
|
||||||
@@ -99,17 +99,15 @@ provide:
|
|||||||
- `script/fmt` — format all code and docs (writes): `script/gofumpt --write` and
|
- `script/fmt` — format all code and docs (writes): `script/gofumpt --write` and
|
||||||
`script/prettier --write`
|
`script/prettier --write`
|
||||||
- `script/gofumpt` — run `gofumpt` over every Go file in the repository in the
|
- `script/gofumpt` — run `gofumpt` over every Go file in the repository in the
|
||||||
given mode, `--write` or `--check`, at the version `bin/tools/go.mod` pins
|
given mode, `--write` or `--check`, at the version `tools/go.mod` requires
|
||||||
(built on demand by `go tool` from source checked against the hashes in
|
(built on demand by `go tool` from source checked against the hashes in
|
||||||
`bin/tools/go.sum`, so nothing installs it); `script/fmt`, `script/fmt-check`
|
`tools/go.sum`, so nothing installs it); `script/fmt`, `script/fmt-check` and
|
||||||
and the Docker lint stage all go through it, so they cannot disagree about Go
|
the Docker lint stage all go through it, so they cannot disagree about Go
|
||||||
formatting
|
formatting
|
||||||
- `script/prettier` — run prettier over the repository's canonical file set
|
- `script/prettier` — run prettier over the repository's canonical file set
|
||||||
(Markdown and JSON, minus `.prettierignore`) in the given mode, `--write` or
|
(Markdown and JSON, minus `.prettierignore`) in the given mode, `--write` or
|
||||||
`--check`, with the prettier version `yarn.lock` pins, run by the node on
|
`--check`; the single definition of that file set, so `script/fmt` and
|
||||||
`PATH` or else the one `script/bootstrap` installed through nvm; the single
|
`script/fmt-check` cannot disagree about it
|
||||||
definition of that file set, so `script/fmt` and `script/fmt-check` cannot
|
|
||||||
disagree about it
|
|
||||||
- `script/fmt-check` — check formatting without writing:
|
- `script/fmt-check` — check formatting without writing:
|
||||||
`script/gofumpt --check` plus `script/prettier --check`
|
`script/gofumpt --check` plus `script/prettier --check`
|
||||||
- `script/check` — run `script/test`, `script/lint`, and `script/fmt-check`
|
- `script/check` — run `script/test`, `script/lint`, and `script/fmt-check`
|
||||||
@@ -271,12 +269,9 @@ are now tracked only in the [issues](https://git.eeqj.de/sneak/mfer/issues).
|
|||||||
cryptographic integrity of downloaded files. A file already there with the
|
cryptographic integrity of downloaded files. A file already there with the
|
||||||
size and hash the manifest lists is skipped. Once every file is in place,
|
size and hash the manifest lists is skipped. Once every file is in place,
|
||||||
the manifest is saved there as `index.mf`, so `mfer check` can verify the
|
the manifest is saved there as `index.mf`, so `mfer check` can verify the
|
||||||
tree later. Each file is downloaded to a temp file beside it, such as
|
tree later. A manifest that lists `index.mf` (in any letter case) or
|
||||||
`.a.txt.tmp` for `a.txt`, then moved into place. A manifest is refused
|
`.index.mf.tmp` at the top of the tree is refused before any file is
|
||||||
before any file is downloaded if it lists a file where fetch writes
|
downloaded, since saving the manifest would replace it.
|
||||||
another: at the temp file of a listed file, or at `index.mf` or
|
|
||||||
`.index.mf.tmp` at the top of the tree. Names are compared in any letter
|
|
||||||
case.
|
|
||||||
- `mfer fetch --require-signature <fingerprint> https://example.com/stuff/`
|
- `mfer fetch --require-signature <fingerprint> https://example.com/stuff/`
|
||||||
- as above, but first refuses a manifest not signed by the key with that
|
- as above, but first refuses a manifest not signed by the key with that
|
||||||
fingerprint, as `mfer check --require-signature` does, before downloading
|
fingerprint, as `mfer check --require-signature` does, before downloading
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// The developer tools this repo runs with `go tool`: gofumpt for
|
|
||||||
// script/gofumpt and protoc-gen-go for script/generate. Kept out of the mfer
|
|
||||||
// module so they add nothing to what mfer's users download. `go tool` builds
|
|
||||||
// exactly the source whose hashes go.sum here records.
|
|
||||||
module sneak.berlin/go/mfer/bin/tools
|
|
||||||
|
|
||||||
go 1.26.0
|
|
||||||
|
|
||||||
tool (
|
|
||||||
google.golang.org/protobuf/cmd/protoc-gen-go
|
|
||||||
mvdan.cc/gofumpt
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
golang.org/x/mod v0.40.0 // indirect
|
|
||||||
golang.org/x/sync v0.22.0 // indirect
|
|
||||||
golang.org/x/tools v0.49.0 // indirect
|
|
||||||
// protoc-gen-go v1.36.11, 2026-10-04
|
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
|
||||||
// gofumpt v0.12.0, 2026-10-04
|
|
||||||
mvdan.cc/gofumpt v0.12.0 // indirect
|
|
||||||
)
|
|
||||||
+17
-35
@@ -91,10 +91,10 @@ var (
|
|||||||
// errHashMismatch indicates a downloaded file whose hash matches no
|
// errHashMismatch indicates a downloaded file whose hash matches no
|
||||||
// manifest hash.
|
// manifest hash.
|
||||||
errHashMismatch = errors.New("hash mismatch")
|
errHashMismatch = errors.New("hash mismatch")
|
||||||
// errNameClash indicates a manifest that lists a file where fetch
|
// errManifestNameListed indicates a manifest that lists a file where
|
||||||
// writes another file.
|
// fetch saves the manifest.
|
||||||
errNameClash = errors.New(
|
errManifestNameListed = errors.New(
|
||||||
"manifest lists a file where fetch writes another file")
|
"manifest lists a file where fetch saves the manifest")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DownloadProgress reports the progress of a single file download.
|
// DownloadProgress reports the progress of a single file download.
|
||||||
@@ -412,7 +412,7 @@ func (mfa *CLIApp) fetchManifestOperation(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// fetchManifest downloads the manifest at manifestURL and parses it,
|
// fetchManifest downloads the manifest at manifestURL and parses it,
|
||||||
// enforcing --require-signature if it is given and refusing a manifest
|
// enforcing --require-signature if it is given and refusing a manifest
|
||||||
// that lists a file where fetch writes another. It returns the manifest as
|
// that lists a file where it will be saved. It returns the manifest as
|
||||||
// downloaded, to be saved once the files are in place, and the files it
|
// downloaded, to be saved once the files are in place, and the files it
|
||||||
// lists.
|
// lists.
|
||||||
func fetchManifest(
|
func fetchManifest(
|
||||||
@@ -452,7 +452,7 @@ func fetchManifest(
|
|||||||
|
|
||||||
files := manifest.Files()
|
files := manifest.Files()
|
||||||
|
|
||||||
err = checkNoNameClash(files)
|
err = checkManifestNameUnlisted(files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -462,37 +462,19 @@ func fetchManifest(
|
|||||||
return manifestData, files, nil
|
return manifestData, files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNoNameClash returns an error if files lists a file, or a directory
|
// checkManifestNameUnlisted returns an error if files lists a file or
|
||||||
// a file is in, under a name where fetch writes another file: the temp
|
// directory at the top of the tree under the name fetch saves the
|
||||||
// file it downloads a listed file to, or, at the top of the tree, the
|
// manifest as, or under that name's temp file. Saving the manifest would
|
||||||
// saved manifest or its temp file. fetch would remove or replace what is
|
// replace or remove it, or fail once every file was downloaded, leaving a
|
||||||
// listed there, or fail partway, leaving a tree check rejects. Names are
|
// tree check rejects. Names are compared ignoring case, since on a
|
||||||
// compared ignoring case, since on a case-insensitive filesystem INDEX.MF
|
// case-insensitive filesystem INDEX.MF and index.mf are one file.
|
||||||
// and index.mf are one file.
|
func checkManifestNameUnlisted(files []*mfer.MFFilePath) error {
|
||||||
func checkNoNameClash(files []*mfer.MFFilePath) error {
|
|
||||||
sep := string(filepath.Separator)
|
|
||||||
|
|
||||||
// written maps each name fetch writes, other than the listed files
|
|
||||||
// themselves, in lower case, to the file it writes there.
|
|
||||||
written := map[string]string{
|
|
||||||
defaultManifestName: "the saved manifest",
|
|
||||||
tempPathFor(defaultManifestName): "the saved manifest's temp file",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
tmpPath := tempPathFor(filepath.Clean(f.GetPath()))
|
top, _, _ := strings.Cut(filepath.Clean(f.GetPath()), string(filepath.Separator))
|
||||||
written[strings.ToLower(tmpPath)] = "the temp file for " + f.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
if strings.EqualFold(top, defaultManifestName) ||
|
||||||
// Look up each directory on the file's path, then the file itself.
|
strings.EqualFold(top, tempPathFor(defaultManifestName)) {
|
||||||
parts := strings.Split(strings.ToLower(filepath.Clean(f.GetPath())), sep)
|
return fmt.Errorf("%w: %s", errManifestNameListed, f.GetPath())
|
||||||
|
|
||||||
for i := range parts {
|
|
||||||
what, ok := written[strings.Join(parts[:i+1], sep)]
|
|
||||||
if ok {
|
|
||||||
return fmt.Errorf("%w: %s (%s)", errNameClash, f.GetPath(), what)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-78
@@ -1176,89 +1176,23 @@ func TestFetchRefusesListedManifestName(t *testing.T) {
|
|||||||
t.Run(listed, func(t *testing.T) {
|
t.Run(listed, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
files := map[string][]byte{listed: []byte("listed")}
|
// Built directly rather than scanned, since a scan lists no
|
||||||
|
// hidden files and never a path starting with "./".
|
||||||
|
content := []byte("listed")
|
||||||
|
builder := mfer.NewBuilder()
|
||||||
|
_, err := builder.AddFile(mfer.RelFilePath(listed), mfer.FileSize(len(content)),
|
||||||
|
mfer.ModTime(time.Now()), bytes.NewReader(content), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
assertFetchRefused(t, builtManifest(t, files), files,
|
var manifest bytes.Buffer
|
||||||
"manifest lists a file where fetch writes another file: "+listed)
|
require.NoError(t, builder.Build(context.Background(), &manifest))
|
||||||
|
|
||||||
|
assertFetchRefused(t, manifest.Bytes(), map[string][]byte{listed: content},
|
||||||
|
"manifest lists a file where fetch saves the manifest: "+listed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFetchRefusesListedTempName fetches manifests that list a.txt and
|
|
||||||
// .a.txt.tmp, the temp file fetch downloads a.txt to, at the top of the
|
|
||||||
// tree and in a directory. Downloading a.txt would remove .a.txt.tmp, so
|
|
||||||
// fetch must refuse the manifest before it creates the destination or
|
|
||||||
// requests any file. README with .README.tmp checks that temp names are
|
|
||||||
// compared ignoring case. A manifest that lists only one of the two is
|
|
||||||
// fetched in full.
|
|
||||||
func TestFetchRefusesListedTempName(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, dir := range []string{"", "sub/"} {
|
|
||||||
for file, tmp := range map[string]string{
|
|
||||||
dir + "a.txt": dir + ".a.txt.tmp",
|
|
||||||
dir + "README": dir + ".README.tmp",
|
|
||||||
} {
|
|
||||||
both := map[string][]byte{
|
|
||||||
file: []byte("a file"),
|
|
||||||
tmp: []byte("a file at its temp name"),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(file+" and "+tmp, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
assertFetchRefused(t, builtManifest(t, both), both,
|
|
||||||
"manifest lists a file where fetch writes another file: "+
|
|
||||||
tmp+" (the temp file for "+file+")")
|
|
||||||
})
|
|
||||||
|
|
||||||
for listed, content := range both {
|
|
||||||
t.Run("only "+listed, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
files := map[string][]byte{listed: content}
|
|
||||||
manifest := builtManifest(t, files)
|
|
||||||
|
|
||||||
server := httptest.NewServer(fetchTestHandler(manifest, files))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
dest := t.TempDir()
|
|
||||||
|
|
||||||
opts := testOpts([]string{
|
|
||||||
testApp, cmdFetch, "-q", "--" + flagDest, dest, server.URL,
|
|
||||||
}, afero.NewOsFs())
|
|
||||||
require.Equal(t, 0, runCLI(opts), testStderr(t, opts))
|
|
||||||
|
|
||||||
want := maps.Clone(files)
|
|
||||||
want[defaultManifestName] = manifest
|
|
||||||
assert.Equal(t, want, filesUnder(t, dest))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// builtManifest returns a manifest of files, built directly rather than
|
|
||||||
// scanned, since a scan lists no hidden files and never a path starting
|
|
||||||
// with "./".
|
|
||||||
func builtManifest(t *testing.T, files map[string][]byte) []byte {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
builder := mfer.NewBuilder()
|
|
||||||
|
|
||||||
for p, content := range files {
|
|
||||||
_, err := builder.AddFile(mfer.RelFilePath(p), mfer.FileSize(len(content)),
|
|
||||||
mfer.ModTime(time.Now()), bytes.NewReader(content), nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var manifest bytes.Buffer
|
|
||||||
|
|
||||||
require.NoError(t, builder.Build(context.Background(), &manifest))
|
|
||||||
|
|
||||||
return manifest.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertFetchRefused serves manifest, a manifest of files, and fetches it
|
// assertFetchRefused serves manifest, a manifest of files, and fetches it
|
||||||
// with flags into a directory that does not exist yet. fetch must fail
|
// with flags into a directory that does not exist yet. fetch must fail
|
||||||
// with message after requesting only the manifest, and must not create
|
// with message after requesting only the manifest, and must not create
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package mfer
|
||||||
|
|
||||||
|
//go:generate protoc ./mf.proto --go_out=paths=source_relative:.
|
||||||
+5
-56
@@ -13,15 +13,11 @@ set -eu
|
|||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
# Pinned versions, 2026-07-06. Never "latest" or "lts"; exact versions.
|
# Pinned versions, 2026-07-06. Never "latest" or "lts"; exact versions.
|
||||||
# The node version is in .nvmrc, where script/prettier reads it too.
|
NODE_VERSION="22.17.0"
|
||||||
NODE_VERSION="$(cat "$ROOT/.nvmrc")"
|
|
||||||
NVM_VERSION="0.40.3"
|
NVM_VERSION="0.40.3"
|
||||||
# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz
|
# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz
|
||||||
NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0"
|
NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0"
|
||||||
YARN_VERSION="1.22.22"
|
YARN_VERSION="1.22.22"
|
||||||
# protoc v33.4, 2026-10-04, for script/generate. The sha256 of each
|
|
||||||
# platform's release archive is in ensure_protoc.
|
|
||||||
PROTOC_VERSION="33.4"
|
|
||||||
|
|
||||||
PKGMGR=""
|
PKGMGR=""
|
||||||
SUDO=""
|
SUDO=""
|
||||||
@@ -78,11 +74,9 @@ verify_sha256() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# nvm is a bash script; run a command in a bash with nvm loaded.
|
# nvm is a bash script; run a command in a bash with nvm loaded
|
||||||
# --no-use: otherwise loading nvm here switches to the version .nvmrc
|
|
||||||
# names, and fails silently while that version is not installed yet.
|
|
||||||
nvm_sh() {
|
nvm_sh() {
|
||||||
bash -c ". \"\$HOME/.nvm/nvm.sh\" --no-use && $*"
|
bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_nvm() {
|
ensure_nvm() {
|
||||||
@@ -128,48 +122,6 @@ install_js_deps() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unpack protoc's release archive for this platform into bin/protoc, after
|
|
||||||
# checking the archive's sha256, unless bin/protoc already holds the pinned
|
|
||||||
# version.
|
|
||||||
ensure_protoc() {
|
|
||||||
dir="$ROOT/bin/protoc"
|
|
||||||
if [ "$("$dir/bin/protoc" --version 2>/dev/null)" = \
|
|
||||||
"libprotoc $PROTOC_VERSION" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
case "$(uname -s) $(uname -m)" in
|
|
||||||
"Linux x86_64")
|
|
||||||
platform="linux-x86_64"
|
|
||||||
sha256="c0040ea9aef08fdeb2c74ca609b18d5fdbfc44ea0042fcfbfb38860d35f7dd66"
|
|
||||||
;;
|
|
||||||
"Linux aarch64" | "Linux arm64")
|
|
||||||
platform="linux-aarch_64"
|
|
||||||
sha256="15aa988f4a6090636525ec236a8e4b3aab41eef402751bd5bb2df6afd9b7b5a5"
|
|
||||||
;;
|
|
||||||
"Darwin x86_64")
|
|
||||||
platform="osx-x86_64"
|
|
||||||
sha256="a49bec10d039e902d3b43e49938c42526f90011467609864fa6386ac4014da58"
|
|
||||||
;;
|
|
||||||
"Darwin arm64")
|
|
||||||
platform="osx-aarch_64"
|
|
||||||
sha256="726297dcfed58592fd35620a5a6246ae020c39e88f3fd4cb1827df7bcf3dfcf1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "bootstrap: no protoc archive pinned for $(uname -s) $(uname -m)" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
if missing curl; then pkg_install curl curl curl curl; fi
|
|
||||||
if missing unzip; then pkg_install unzip unzip unzip unzip; fi
|
|
||||||
tmp="$(mktemp -d)"
|
|
||||||
curl -fsSL -o "$tmp/protoc.zip" \
|
|
||||||
"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${platform}.zip"
|
|
||||||
verify_sha256 "$tmp/protoc.zip" "$sha256"
|
|
||||||
rm -rf "$dir"
|
|
||||||
unzip -q "$tmp/protoc.zip" -d "$dir"
|
|
||||||
rm -rf "$tmp"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
@@ -190,12 +142,9 @@ main() {
|
|||||||
|
|
||||||
# ---- Go repos ----
|
# ---- Go repos ----
|
||||||
if missing go; then pkg_install go golang go go; fi
|
if missing go; then pkg_install go golang go go; fi
|
||||||
# No golangci-lint: script/lint runs it in Docker only.
|
# No golangci-lint: script/lint runs it in Docker only. No gofumpt:
|
||||||
|
# script/gofumpt builds the version tools/go.mod requires.
|
||||||
go mod download
|
go mod download
|
||||||
# gofumpt and protoc-gen-go: bin/tools/go.mod pins them, and
|
|
||||||
# script/gofumpt and script/generate build them from there.
|
|
||||||
(cd "$ROOT/bin/tools" && go mod download)
|
|
||||||
ensure_protoc
|
|
||||||
|
|
||||||
# ---- Python repos ----
|
# ---- Python repos ----
|
||||||
# if missing python3; then pkg_install python3 python3 python3 python3; fi
|
# if missing python3; then pkg_install python3 python3 python3 python3; fi
|
||||||
|
|||||||
+21
-20
@@ -4,17 +4,26 @@
|
|||||||
# regenerates mf.pb.go: it is committed, so building and checking need no
|
# regenerates mf.pb.go: it is committed, so building and checking need no
|
||||||
# protoc. A test fails while mf.proto no longer matches the recorded hash.
|
# protoc. A test fails while mf.proto no longer matches the recorded hash.
|
||||||
#
|
#
|
||||||
# Runs the protoc that script/bootstrap unpacks into bin/protoc, and the
|
# Needs exactly the protoc and protoc-gen-go versions named in the header of
|
||||||
# protoc-gen-go that bin/tools/go.mod pins. Another version of either
|
# the committed mf.pb.go (README.md says how to install them). Another
|
||||||
# writes a different mf.pb.go.
|
# version writes a different mf.pb.go, so the script refuses to run.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
# The protoc version script/bootstrap installs. protoc 33.4 names itself
|
# protoc 33.4 names itself v6.33.4 in the mf.pb.go header.
|
||||||
# v6.33.4 in the mf.pb.go header.
|
|
||||||
PROTOC_VERSION="33.4"
|
PROTOC_VERSION="33.4"
|
||||||
PROTOC="$ROOT/bin/protoc/bin/protoc"
|
PROTOC_GEN_GO_VERSION="v1.36.11"
|
||||||
|
|
||||||
|
# require_version <command> <its exact --version output>
|
||||||
|
require_version() {
|
||||||
|
actual="$("$1" --version 2>/dev/null || true)"
|
||||||
|
if [ "$actual" != "$2" ]; then
|
||||||
|
echo "generate: needs $2 on PATH, found: ${actual:-none}" >&2
|
||||||
|
echo " README.md says how to install it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# sha256 <file>: print "<hash> <file>", with sha256sum, or with shasum
|
# sha256 <file>: print "<hash> <file>", with sha256sum, or with shasum
|
||||||
# where there is no sha256sum.
|
# where there is no sha256sum.
|
||||||
@@ -30,24 +39,16 @@ sha256() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
# A bin/protoc left from before the pin moved fails here, until
|
|
||||||
# script/bootstrap replaces it.
|
|
||||||
actual="$("$PROTOC" --version 2>/dev/null || true)"
|
|
||||||
if [ "$actual" != "libprotoc $PROTOC_VERSION" ]; then
|
|
||||||
echo "generate: needs protoc $PROTOC_VERSION in bin/protoc," \
|
|
||||||
"found: ${actual:-none}; run script/bootstrap" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# `go tool -n` builds protoc-gen-go from bin/tools and prints where the
|
|
||||||
# binary is, without running it.
|
|
||||||
plugin="$(cd "$ROOT/bin/tools" && go tool -n protoc-gen-go)"
|
|
||||||
|
|
||||||
cd "$ROOT/mfer"
|
cd "$ROOT/mfer"
|
||||||
|
# `go install` puts protoc-gen-go in $(go env GOPATH)/bin, which is
|
||||||
|
# often not on PATH.
|
||||||
|
PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
require_version protoc "libprotoc $PROTOC_VERSION"
|
||||||
|
require_version protoc-gen-go "protoc-gen-go $PROTOC_GEN_GO_VERSION"
|
||||||
# Hashed before regenerating, so a missing hash tool stops the script
|
# Hashed before regenerating, so a missing hash tool stops the script
|
||||||
# before it changes anything. Regenerating leaves mf.proto as it is.
|
# before it changes anything. Regenerating leaves mf.proto as it is.
|
||||||
proto_hash="$(sha256 mf.proto)"
|
proto_hash="$(sha256 mf.proto)"
|
||||||
"$PROTOC" --plugin=protoc-gen-go="$plugin" \
|
go generate .
|
||||||
--go_out=paths=source_relative:. ./mf.proto
|
|
||||||
echo "$proto_hash" >mf.proto.sha256
|
echo "$proto_hash" >mf.proto.sha256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -10,10 +10,9 @@ set -eu
|
|||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
# The gofumpt version is the one bin/tools/go.mod pins. `go tool` run in
|
# gofumpt v0.12.0, 2026-10-04, required by tools/go.mod. `go tool` run in
|
||||||
# bin/tools builds it from source checked against the hashes in
|
# tools/ builds it from source checked against the hashes in tools/go.sum,
|
||||||
# bin/tools/go.sum, so neither a developer machine nor the lint image needs
|
# so neither a developer machine nor the lint image needs it installed.
|
||||||
# it installed.
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "usage: script/gofumpt --write|--check" >&2
|
echo "usage: script/gofumpt --write|--check" >&2
|
||||||
@@ -22,7 +21,7 @@ usage() {
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
[ "$#" -eq 1 ] || usage
|
[ "$#" -eq 1 ] || usage
|
||||||
cd "$ROOT/bin/tools"
|
cd "$ROOT/tools"
|
||||||
# Every Go file in the repo, from $ROOT down. gofumpt holds generated
|
# Every Go file in the repo, from $ROOT down. gofumpt holds generated
|
||||||
# files, such as mfer/mf.pb.go, to gofmt's rules only.
|
# files, such as mfer/mf.pb.go, to gofmt's rules only.
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|||||||
+4
-12
@@ -31,18 +31,9 @@ main() {
|
|||||||
|
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
# Where there is no node on PATH, script/bootstrap installs the version
|
|
||||||
# .nvmrc names through nvm, which keeps it in this directory.
|
|
||||||
if ! command -v node >/dev/null 2>&1; then
|
|
||||||
PATH="$HOME/.nvm/versions/node/v$(cat .nvmrc)/bin:$PATH"
|
|
||||||
fi
|
|
||||||
if ! command -v node >/dev/null 2>&1; then
|
|
||||||
echo "prettier: node is missing; run script/bootstrap" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# node_modules keeps the old prettier after package.json moves to a new
|
# node_modules keeps the old prettier after package.json moves to a new
|
||||||
# one, until script/bootstrap runs again, so compare the two.
|
# one, until script/bootstrap runs again, so compare the two. A prettier
|
||||||
|
# that runs also means node is on PATH, which reading package.json needs.
|
||||||
if ! installed="$("$PRETTIER" --version 2>/dev/null)"; then
|
if ! installed="$("$PRETTIER" --version 2>/dev/null)"; then
|
||||||
echo "prettier: not installed; run script/bootstrap" >&2
|
echo "prettier: not installed; run script/bootstrap" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -63,7 +54,8 @@ main() {
|
|||||||
# patterns always match at least one tracked file (README.md,
|
# patterns always match at least one tracked file (README.md,
|
||||||
# package.json), so an empty match means the glob broke, and prettier
|
# package.json), so an empty match means the glob broke, and prettier
|
||||||
# erroring out is exactly what we want rather than a vacuous pass.
|
# erroring out is exactly what we want rather than a vacuous pass.
|
||||||
"$PRETTIER" "$mode" "**/*.md" "**/*.json"
|
"$PRETTIER" "$mode" "**/*.md"
|
||||||
|
"$PRETTIER" "$mode" "**/*.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// The developer tools this repo runs with `go tool`, kept out of the mfer
|
||||||
|
// module so they add nothing to what mfer's users download. `go tool` builds
|
||||||
|
// exactly the source whose hashes go.sum here records.
|
||||||
|
module sneak.berlin/go/mfer/tools
|
||||||
|
|
||||||
|
go 1.26.0
|
||||||
|
|
||||||
|
tool mvdan.cc/gofumpt
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/mod v0.40.0 // indirect
|
||||||
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
|
golang.org/x/tools v0.49.0 // indirect
|
||||||
|
mvdan.cc/gofumpt v0.12.0 // indirect
|
||||||
|
)
|
||||||
@@ -16,7 +16,5 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
|||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
||||||
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
|
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
|
||||||
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
|
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
|
||||||
Reference in New Issue
Block a user