blob: b3e88ae10fa17fb199ff70a511d9b11c0f011ba9 [file] [log] [blame]
// 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.
package task
import (
"archive/tar"
"archive/zip"
"compress/flate"
"compress/gzip"
"fmt"
"io"
"strings"
"time"
"golang.org/x/build/internal/releasetargets"
)
// Converted distributions are treated as versions of golang.org/toolchain.
// The archive for go1.2.3.linux-amd64.tar.gz is stored as version v0.0.1-go1.2.3.linux-amd64.
const (
modulePath = "golang.org/toolchain"
moduleVersion = "v0.0.1"
)
func ToolchainZipPrefix(target *releasetargets.Target, version string) string {
return modulePath + "@" + ToolchainModuleVersion(target, version)
}
func ToolchainModuleVersion(target *releasetargets.Target, version string) string {
return fmt.Sprintf("%v-%v.%v-%v", moduleVersion, version, target.GOOS, target.GOARCH)
}
// TarToModFiles converts the distribution archive with the given name and content
// to a collection of module files.
func TarToModFiles(target *releasetargets.Target, version string, t time.Time, tgz io.Reader, w io.Writer) (mod string, info string, _ error) {
vers := ToolchainModuleVersion(target, version)
zipPrefix := ToolchainZipPrefix(target, version)
// rename takes the name of a file found in a distribution archive
// and returns the name to use for that file in the module archive.
// The main conversion is go/zzz -> golang.org/toolchain@v0.0.1-go<vers>.<goos>-<goarch>/zzz.
// If rename returns "", nil, then the file should be omitted from the
// module archive entirely.
rename := func(name string) (string, error) {
if !strings.HasPrefix(name, "go/") {
return "", fmt.Errorf("unexpected file name %q", name)
}
// Modules cannot contain go.mod files, so rename them to _go.mod.
if strings.HasSuffix(name, "/go.mod") {
name = strings.TrimSuffix(name, "/go.mod") + "/_go.mod"
}
// Omit these directories.
switch {
case strings.HasPrefix(name, "go/.github/"),
strings.HasPrefix(name, "go/api/"),
strings.HasPrefix(name, "go/doc/"),
strings.HasPrefix(name, "go/misc/"),
strings.HasPrefix(name, "go/test/"):
return "", nil
}
return zipPrefix + name[len("go"):], nil
}
// Convert archive, extracting its modification time for our metadata.
zw := zip.NewWriter(w)
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
return flate.NewWriter(out, flate.BestCompression)
})
if err := convertTarGz(zw, t, tgz, rename); err == nil {
err = zw.Close()
}
info = fmt.Sprintf("{%q:%q, %q:%q}\n", "Version", vers, "Time", t.UTC().Format(time.RFC3339))
mod = fmt.Sprintf("module %s\n", modulePath)
return mod, info, nil
}
// convertTarGz writes a distribution .tar.gz archive's content to zw, applying the rename function.
func convertTarGz(zw *zip.Writer, t time.Time, tgz io.Reader, rename func(string) (string, error)) error {
gzr, err := gzip.NewReader(tgz)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
hdr, err := tr.Next()
if err != nil {
if err == io.EOF {
break
}
return err
}
if hdr.Typeflag != tar.TypeReg { // omit directories
continue
}
name, err := rename(hdr.Name)
if err != nil {
return err
}
if name == "" { // omit files rejected by rename
continue
}
w, err := zw.CreateHeader(&zip.FileHeader{
Name: name,
Method: zip.Deflate,
Modified: t,
})
if err != nil {
return err
}
if _, err := io.Copy(w, tr); err != nil {
return err
}
}
return nil
}