mirror of
https://github.com/tinygo-org/net.git
synced 2026-08-16 10:43:29 +00:00
Replace upgrade.sh with Go-based upgrade tool in tools/upgrade/
Rewrite the bash upgrade script as a standalone Go program for better portability and maintainability. The tool preserves all existing behavior: three-category file classification, dry-run mode, Go source resolution (gvm/GOROOT/download), 3-way merge via diff3, and colored output. Build: cd tools/upgrade && go build -o upgrade . Run: ./tools/upgrade/upgrade --dry-run Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -23,3 +23,5 @@ go.work
|
|||||||
# Upgrade script work/report directories
|
# Upgrade script work/report directories
|
||||||
.upgrade-work/
|
.upgrade-work/
|
||||||
.upgrade-report/
|
.upgrade-report/
|
||||||
|
|
||||||
|
./tools/upgrade/upgrade
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# upgrade
|
||||||
|
|
||||||
|
Automates the TinyGo `net` package upgrade process by backporting
|
||||||
|
changes from upstream Go stdlib and generating comparison reports.
|
||||||
|
|
||||||
|
It handles three categories of files:
|
||||||
|
|
||||||
|
- **TinyGo-only** (e.g. `netdev.go`, `tlssock.go`) — skipped entirely
|
||||||
|
- **Unmodified copies** — replaced directly from upstream
|
||||||
|
- **Modified files** (with `// TINYGO` markers) — 3-way merged via `diff3`
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- `diff` and `diff3` (from GNU diffutils)
|
||||||
|
- Go source trees are resolved automatically:
|
||||||
|
1. gvm installs at `~/.gvm/gos/`
|
||||||
|
2. Active `GOROOT` if the version matches
|
||||||
|
3. Downloaded from `https://go.dev/dl/` into `.upgrade-work/`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd tools/upgrade
|
||||||
|
go build -o upgrade .
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces the `upgrade` binary. It must be run from the `net`
|
||||||
|
package root directory (the repository root).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# From the net package root:
|
||||||
|
./tools/upgrade/upgrade [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `--dry-run` | `false` | Preview changes without modifying files |
|
||||||
|
| `--cur` | `1.21.4` | Go version the TinyGo net package is currently based on |
|
||||||
|
| `--upstream` | `1.26.2` | Target upstream Go version to upgrade to |
|
||||||
|
| `--file` | | Process a single file instead of all files |
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Preview what would change
|
||||||
|
./tools/upgrade/upgrade --dry-run
|
||||||
|
|
||||||
|
# Perform the upgrade
|
||||||
|
./tools/upgrade/upgrade --cur 1.21.4 --upstream 1.26.2
|
||||||
|
|
||||||
|
# Preview a single file
|
||||||
|
./tools/upgrade/upgrade --dry-run --file dial.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Reports are written to `.upgrade-report/` in the net package root:
|
||||||
|
|
||||||
|
- `summary.txt` — one-line status per file
|
||||||
|
- `diffs/` — unified diffs showing upstream and TinyGo changes
|
||||||
|
- `merged/` — merge results (only in apply mode)
|
||||||
|
|
||||||
|
Files with merge conflicts are saved as `<file>.conflicted` alongside
|
||||||
|
the original.
|
||||||
|
|
||||||
|
## After upgrading
|
||||||
|
|
||||||
|
1. Review and resolve any `.conflicted` files
|
||||||
|
2. Check for files missing from upstream (may have been renamed/removed)
|
||||||
|
3. Verify `// TINYGO` comments are preserved
|
||||||
|
4. Test with TinyGo example/net examples
|
||||||
|
5. Update `README.md` version references
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module github.com/tinygo-org/net/tools/upgrade
|
||||||
|
|
||||||
|
go 1.22
|
||||||
@@ -0,0 +1,787 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bufio"
|
||||||
|
"compress/gzip"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultCurVersion = "1.21.4"
|
||||||
|
defaultUpstreamVersion = "1.26.2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── Color helpers ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const (
|
||||||
|
colorRed = "\033[0;31m"
|
||||||
|
colorGreen = "\033[0;32m"
|
||||||
|
colorYellow = "\033[1;33m"
|
||||||
|
colorBlue = "\033[0;34m"
|
||||||
|
colorReset = "\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func info(msg string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, colorBlue+"[INFO]"+colorReset+" "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
func success(msg string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, colorGreen+"[OK]"+colorReset+" "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
func warn(msg string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, colorYellow+"[WARN]"+colorReset+" "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
func errorf(msg string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, colorRed+"[ERROR]"+colorReset+" "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── File classification ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Files entirely new to TinyGo (not from upstream) — never overwrite these.
|
||||||
|
var tinygoOnlyFiles = map[string]bool{
|
||||||
|
"netdev.go": true,
|
||||||
|
"tlssock.go": true,
|
||||||
|
"README.md": true,
|
||||||
|
"LICENSE": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files that are straight copies from upstream (no TINYGO modifications).
|
||||||
|
var unmodifiedFiles = []string{
|
||||||
|
"ip.go",
|
||||||
|
"mac.go",
|
||||||
|
"mac_test.go",
|
||||||
|
"parse.go",
|
||||||
|
"pipe.go",
|
||||||
|
"http/clone.go",
|
||||||
|
"http/cookie.go",
|
||||||
|
"http/fs.go",
|
||||||
|
"http/http.go",
|
||||||
|
"http/jar.go",
|
||||||
|
"http/method.go",
|
||||||
|
"http/sniff.go",
|
||||||
|
"http/status.go",
|
||||||
|
"http/internal/ascii/print.go",
|
||||||
|
"http/internal/ascii/print_test.go",
|
||||||
|
"http/internal/chunked.go",
|
||||||
|
"http/internal/chunked_test.go",
|
||||||
|
"http/httptest/recorder.go",
|
||||||
|
"http/httputil/dump.go",
|
||||||
|
"http/httputil/httputil.go",
|
||||||
|
"http/httputil/persist.go",
|
||||||
|
"http/httputil/reverseproxy.go",
|
||||||
|
"http/pprof/pprof.go",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files copied AND modified for TinyGo (contain // TINYGO markers).
|
||||||
|
// These need 3-way merge: upstream changes applied to TinyGo-modified version.
|
||||||
|
var modifiedFiles = []string{
|
||||||
|
"dial.go",
|
||||||
|
"interface.go",
|
||||||
|
"iprawsock.go",
|
||||||
|
"ipsock.go",
|
||||||
|
"lookup.go",
|
||||||
|
"lookup_unix.go",
|
||||||
|
"lookup_windows.go",
|
||||||
|
"net.go",
|
||||||
|
"tcpsock.go",
|
||||||
|
"udpsock.go",
|
||||||
|
"unixsock.go",
|
||||||
|
"http/client.go",
|
||||||
|
"http/header.go",
|
||||||
|
"http/pattern.go",
|
||||||
|
"http/request.go",
|
||||||
|
"http/response.go",
|
||||||
|
"http/server.go",
|
||||||
|
"http/transfer.go",
|
||||||
|
"http/transport.go",
|
||||||
|
"http/httptest/httptest.go",
|
||||||
|
"http/httptest/server.go",
|
||||||
|
"http/httptrace/trace.go",
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifiedFilesSet map[string]bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modifiedFilesSet = make(map[string]bool, len(modifiedFiles))
|
||||||
|
for _, f := range modifiedFiles {
|
||||||
|
modifiedFilesSet[f] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Map TinyGo paths to upstream Go stdlib paths ───────────────────────────
|
||||||
|
|
||||||
|
func upstreamPath(file string) string {
|
||||||
|
return "src/net/" + file
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Detect per-file CUR version from TINYGO header comment ─────────────────
|
||||||
|
|
||||||
|
var versionRe = regexp.MustCompile(`Go (\d+\.\d+\.\d+)`)
|
||||||
|
|
||||||
|
func detectCurVersion(filePath, fallback string) string {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
if scanner.Scan() {
|
||||||
|
if m := versionRe.FindStringSubmatch(scanner.Text()); m != nil {
|
||||||
|
return m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Resolve Go source trees ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func resolveGoSource(version, workDir string) (string, error) {
|
||||||
|
// Try gvm install
|
||||||
|
gvmPath := filepath.Join(os.Getenv("HOME"), ".gvm", "gos", "go"+version)
|
||||||
|
if isDir(filepath.Join(gvmPath, "src", "net")) {
|
||||||
|
return gvmPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try current GOROOT if active Go version matches
|
||||||
|
goroot, _ := execOutput("go", "env", "GOROOT")
|
||||||
|
goroot = strings.TrimSpace(goroot)
|
||||||
|
if goroot != "" && isDir(filepath.Join(goroot, "src", "net")) {
|
||||||
|
activeVer, _ := execOutput("go", "version")
|
||||||
|
if m := versionRe.FindStringSubmatch(activeVer); m != nil && m[1] == version {
|
||||||
|
return goroot, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download
|
||||||
|
return downloadGoSource(version, workDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadGoSource(version, workDir string) (string, error) {
|
||||||
|
destDir := filepath.Join(workDir, "go"+version)
|
||||||
|
if isDir(filepath.Join(destDir, "src", "net")) {
|
||||||
|
info("Go %s source already cached at %s", version, destDir)
|
||||||
|
return destDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://go.dev/dl/go%s.src.tar.gz", version)
|
||||||
|
tarball := filepath.Join(workDir, fmt.Sprintf("go%s.src.tar.gz", version))
|
||||||
|
|
||||||
|
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
||||||
|
return "", fmt.Errorf("creating work dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(tarball); os.IsNotExist(err) {
|
||||||
|
info("Downloading Go %s source from %s ...", version, url)
|
||||||
|
if err := downloadFile(tarball, url); err != nil {
|
||||||
|
return "", fmt.Errorf("downloading %s: %w", url, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info("Extracting Go %s source...", version)
|
||||||
|
if err := extractTarGz(tarball, destDir); err != nil {
|
||||||
|
return "", fmt.Errorf("extracting tarball: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success("Go %s source ready at %s", version, destDir)
|
||||||
|
return destDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFile(dest, url string) error {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("HTTP %d for %s", resp.StatusCode, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTarGz(tarball, destDir string) error {
|
||||||
|
f, err := os.Open(tarball)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
gz, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gz)
|
||||||
|
for {
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip first component ("go/") to match --strip-components=1
|
||||||
|
parts := strings.SplitN(hdr.Name, "/", 2)
|
||||||
|
if len(parts) < 2 || parts[1] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target := filepath.Join(destDir, parts[1])
|
||||||
|
|
||||||
|
switch hdr.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(hdr.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(out, tr); err != nil {
|
||||||
|
out.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Process unmodified (straight copy) files ────────────────────────────────
|
||||||
|
|
||||||
|
type summaryEntry struct {
|
||||||
|
status string
|
||||||
|
file string
|
||||||
|
detail string
|
||||||
|
}
|
||||||
|
|
||||||
|
type upgrader struct {
|
||||||
|
curVersion string
|
||||||
|
upstreamVersion string
|
||||||
|
dryRun bool
|
||||||
|
tinygoNetDir string
|
||||||
|
workDir string
|
||||||
|
reportDir string
|
||||||
|
curRoot string
|
||||||
|
upstreamRoot string
|
||||||
|
summary []summaryEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *upgrader) addSummary(status, file, detail string) {
|
||||||
|
u.summary = append(u.summary, summaryEntry{status, file, detail})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *upgrader) processUnmodified(file string) {
|
||||||
|
upath := upstreamPath(file)
|
||||||
|
upstreamFile := filepath.Join(u.upstreamRoot, upath)
|
||||||
|
tinygoFile := filepath.Join(u.tinygoNetDir, file)
|
||||||
|
|
||||||
|
if !fileExists(upstreamFile) {
|
||||||
|
warn("MISSING in upstream %s: %s", u.upstreamVersion, upath)
|
||||||
|
u.addSummary("MISSING_UPSTREAM", file, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
curFile := filepath.Join(u.curRoot, upath)
|
||||||
|
|
||||||
|
if fileExists(curFile) && filesEqual(curFile, upstreamFile) {
|
||||||
|
u.addSummary("UNCHANGED", file, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.dryRun {
|
||||||
|
info("[DRY-RUN] Would replace %s with upstream %s copy", file, u.upstreamVersion)
|
||||||
|
if fileExists(curFile) {
|
||||||
|
diffPath := filepath.Join(u.reportDir, "diffs", file+".diff")
|
||||||
|
writeDiff(curFile, upstreamFile, diffPath)
|
||||||
|
lines := countLines(diffPath)
|
||||||
|
info(" Upstream diff: %d lines (see .upgrade-report/diffs/%s.diff)", lines, file)
|
||||||
|
}
|
||||||
|
u.addSummary("WOULD_COPY", file, "")
|
||||||
|
} else {
|
||||||
|
header := firstLine(tinygoFile)
|
||||||
|
if strings.HasPrefix(header, "// TINYGO") {
|
||||||
|
oldVer := detectCurVersion(tinygoFile, u.curVersion)
|
||||||
|
upstreamContent, err := os.ReadFile(upstreamFile)
|
||||||
|
if err != nil {
|
||||||
|
errorf("reading upstream file %s: %v", upstreamFile, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Update version in header and prepend to upstream content (minus first line)
|
||||||
|
newHeader := strings.Replace(header, "Go "+oldVer, "Go "+u.upstreamVersion, 1)
|
||||||
|
// Find the first newline in upstream content to skip its first line
|
||||||
|
upstreamLines := string(upstreamContent)
|
||||||
|
if idx := strings.Index(upstreamLines, "\n"); idx >= 0 {
|
||||||
|
upstreamLines = upstreamLines[idx:]
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(tinygoFile, []byte(newHeader+upstreamLines), 0o644); err != nil {
|
||||||
|
errorf("writing %s: %v", tinygoFile, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := copyFile(upstreamFile, tinygoFile); err != nil {
|
||||||
|
errorf("copying %s: %v", file, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.addSummary("COPIED", file, "")
|
||||||
|
success("Copied %s from upstream %s", file, u.upstreamVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Process modified (3-way merge) files ────────────────────────────────────
|
||||||
|
|
||||||
|
func (u *upgrader) processModified(file string) {
|
||||||
|
upath := upstreamPath(file)
|
||||||
|
upstreamFile := filepath.Join(u.upstreamRoot, upath)
|
||||||
|
tinygoFile := filepath.Join(u.tinygoNetDir, file)
|
||||||
|
|
||||||
|
if !fileExists(upstreamFile) {
|
||||||
|
warn("MISSING in upstream %s: %s", u.upstreamVersion, upath)
|
||||||
|
u.addSummary("MISSING_UPSTREAM", file, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect the actual CUR version for this specific file
|
||||||
|
fileCurVersion := detectCurVersion(tinygoFile, u.curVersion)
|
||||||
|
fileCurRoot := u.curRoot
|
||||||
|
|
||||||
|
if fileCurVersion != u.curVersion {
|
||||||
|
info("%s: based on Go %s (not default %s)", file, fileCurVersion, u.curVersion)
|
||||||
|
var err error
|
||||||
|
fileCurRoot, err = resolveGoSource(fileCurVersion, u.workDir)
|
||||||
|
if err != nil {
|
||||||
|
errorf("resolving Go %s source: %v", fileCurVersion, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curFile := filepath.Join(fileCurRoot, upath)
|
||||||
|
|
||||||
|
if !fileExists(curFile) {
|
||||||
|
warn("MISSING in CUR %s: %s — can only diff against current TinyGo version", fileCurVersion, upath)
|
||||||
|
u.addSummary("MISSING_CUR", file, "")
|
||||||
|
diffPath := filepath.Join(u.reportDir, "diffs", file+".upstream-vs-tinygo.diff")
|
||||||
|
writeDiff(tinygoFile, upstreamFile, diffPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if upstream even changed this file
|
||||||
|
if filesEqual(curFile, upstreamFile) {
|
||||||
|
u.addSummary("UNCHANGED", file, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate upstream diff (what changed from CUR -> UPSTREAM in official Go)
|
||||||
|
upstreamDiffPath := filepath.Join(u.reportDir, "diffs", file+".upstream-changes.diff")
|
||||||
|
writeDiff(curFile, upstreamFile, upstreamDiffPath)
|
||||||
|
|
||||||
|
// Generate current tinygo diff (what TinyGo changed from CUR)
|
||||||
|
tinygoDiffPath := filepath.Join(u.reportDir, "diffs", file+".tinygo-changes.diff")
|
||||||
|
writeDiff(curFile, tinygoFile, tinygoDiffPath)
|
||||||
|
|
||||||
|
upstreamLines := countLines(upstreamDiffPath)
|
||||||
|
|
||||||
|
if u.dryRun {
|
||||||
|
info("[DRY-RUN] Would 3-way merge %s (Go %s → %s)", file, fileCurVersion, u.upstreamVersion)
|
||||||
|
info(" Upstream changes: %d diff lines", upstreamLines)
|
||||||
|
info(" See: .upgrade-report/diffs/%s.upstream-changes.diff", file)
|
||||||
|
info(" See: .upgrade-report/diffs/%s.tinygo-changes.diff", file)
|
||||||
|
u.addSummary("WOULD_MERGE", file, fmt.Sprintf("(%d upstream diff lines)", upstreamLines))
|
||||||
|
} else {
|
||||||
|
// Attempt 3-way merge using diff3
|
||||||
|
mergedPath := filepath.Join(u.reportDir, "merged", file)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(mergedPath), 0o755); err != nil {
|
||||||
|
errorf("creating merge dir: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// diff3 -m tinygoFile curFile upstreamFile
|
||||||
|
cmd := exec.Command("diff3", "-m", tinygoFile, curFile, upstreamFile)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
|
||||||
|
// Write merged output regardless of exit code
|
||||||
|
if writeErr := os.WriteFile(mergedPath, output, 0o644); writeErr != nil {
|
||||||
|
errorf("writing merged file: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Clean merge
|
||||||
|
if cpErr := copyFile(mergedPath, tinygoFile); cpErr != nil {
|
||||||
|
errorf("copying merged file: %v", cpErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update version header
|
||||||
|
updateVersionHeader(tinygoFile, u.upstreamVersion)
|
||||||
|
|
||||||
|
u.addSummary("MERGED_CLEAN", file, "")
|
||||||
|
success("Merged %s cleanly (Go %s → %s)", file, fileCurVersion, u.upstreamVersion)
|
||||||
|
} else {
|
||||||
|
// Merge conflicts
|
||||||
|
conflictedPath := tinygoFile + ".conflicted"
|
||||||
|
if cpErr := copyFile(mergedPath, conflictedPath); cpErr != nil {
|
||||||
|
errorf("copying conflicted file: %v", cpErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conflicts := countOccurrences(mergedPath, "<<<<<<<")
|
||||||
|
|
||||||
|
u.addSummary("CONFLICTS", file, fmt.Sprintf("(%d conflicts)", conflicts))
|
||||||
|
warn("CONFLICTS in %s: %d conflict(s)", file, conflicts)
|
||||||
|
warn(" Conflicted merge saved to: %s.conflicted", file)
|
||||||
|
warn(" Upstream changes: .upgrade-report/diffs/%s.upstream-changes.diff", file)
|
||||||
|
warn(" TinyGo changes: .upgrade-report/diffs/%s.tinygo-changes.diff", file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateVersionHeader(filePath, newVersion string) {
|
||||||
|
data, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lines := strings.SplitN(string(data), "\n", 2)
|
||||||
|
if len(lines) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m := versionRe.FindStringSubmatch(lines[0]); m != nil {
|
||||||
|
lines[0] = strings.Replace(lines[0], "Go "+m[1], "Go "+newVersion, 1)
|
||||||
|
os.WriteFile(filePath, []byte(strings.Join(lines, "\n")), 0o644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Summary ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (u *upgrader) printSummary() {
|
||||||
|
counts := make(map[string]int)
|
||||||
|
for _, e := range u.summary {
|
||||||
|
counts[e.status]++
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||||
|
fmt.Println(" Summary")
|
||||||
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Printf(" Unchanged (no upstream changes): %d\n", counts["UNCHANGED"])
|
||||||
|
if u.dryRun {
|
||||||
|
fmt.Printf(" Would copy (unmodified): %d\n", counts["WOULD_COPY"])
|
||||||
|
fmt.Printf(" Would merge (modified): %d\n", counts["WOULD_MERGE"])
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Copied (unmodified): %d\n", counts["COPIED"])
|
||||||
|
fmt.Printf(" Merged cleanly: %d\n", counts["MERGED_CLEAN"])
|
||||||
|
fmt.Printf(" CONFLICTS (need manual fix): %d\n", counts["CONFLICTS"])
|
||||||
|
}
|
||||||
|
if counts["MISSING_UPSTREAM"] > 0 {
|
||||||
|
fmt.Printf(" Missing in upstream: %d\n", counts["MISSING_UPSTREAM"])
|
||||||
|
}
|
||||||
|
if counts["MISSING_CUR"] > 0 {
|
||||||
|
fmt.Printf(" Missing in CUR baseline: %d\n", counts["MISSING_CUR"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if counts["CONFLICTS"] > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
warn("Files with merge conflicts:")
|
||||||
|
for _, e := range u.summary {
|
||||||
|
if e.status == "CONFLICTS" {
|
||||||
|
fmt.Printf(" %s %s\n", e.file, e.detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.dryRun && counts["WOULD_MERGE"] > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
info("Modified files that need merging:")
|
||||||
|
for _, e := range u.summary {
|
||||||
|
if e.status == "WOULD_MERGE" {
|
||||||
|
fmt.Printf(" %s %s\n", e.file, e.detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(" Full report: %s/\n", u.reportDir)
|
||||||
|
fmt.Printf(" Diffs: %s/diffs/\n", u.reportDir)
|
||||||
|
if !u.dryRun {
|
||||||
|
fmt.Printf(" Merged: %s/merged/\n", u.reportDir)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if u.dryRun {
|
||||||
|
info("Dry run complete. Review the report, then run without --dry-run to apply.")
|
||||||
|
} else {
|
||||||
|
info("Upgrade applied. Review changes, resolve any conflicts, then test.")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Next steps:")
|
||||||
|
fmt.Println(" 1. Review and resolve any .conflicted files")
|
||||||
|
fmt.Println(" 2. Check for files missing from upstream (may have been renamed/removed)")
|
||||||
|
fmt.Println(" 3. Verify TINYGO comments are preserved")
|
||||||
|
fmt.Println(" 4. Test with TinyGo example/net examples")
|
||||||
|
fmt.Println(" 5. Update README.md version references")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Write summary.txt ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (u *upgrader) writeSummaryFile() {
|
||||||
|
path := filepath.Join(u.reportDir, "summary.txt")
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
for _, e := range u.summary {
|
||||||
|
if e.detail != "" {
|
||||||
|
fmt.Fprintf(f, "%s %s %s\n", e.status, e.file, e.detail)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f, "%s %s\n", e.status, e.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Utility functions ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func isDir(path string) bool {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
return err == nil && fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesEqual(a, b string) bool {
|
||||||
|
dataA, errA := os.ReadFile(a)
|
||||||
|
dataB, errB := os.ReadFile(b)
|
||||||
|
if errA != nil || errB != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return string(dataA) == string(dataB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstLine(path string) string {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
if scanner.Scan() {
|
||||||
|
return scanner.Text()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(dst, data, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDiff(fileA, fileB, outPath string) {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd := exec.Command("diff", "-u", fileA, fileB)
|
||||||
|
// diff returns exit code 1 when files differ, which is expected
|
||||||
|
output, _ := cmd.CombinedOutput()
|
||||||
|
os.WriteFile(outPath, output, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func countLines(path string) int {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
count := 0
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func countOccurrences(path, needle string) int {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return strings.Count(string(data), needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execOutput(name string, args ...string) (string, error) {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
curVersion := flag.String("cur", defaultCurVersion, "Current Go version the TinyGo net package is based on")
|
||||||
|
upstreamVersion := flag.String("upstream", defaultUpstreamVersion, "Target upstream Go version to upgrade to")
|
||||||
|
dryRun := flag.Bool("dry-run", false, "Preview what would change without modifying files")
|
||||||
|
singleFile := flag.String("file", "", "Process only a single file")
|
||||||
|
help := flag.Bool("help", false, "Show help")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *help {
|
||||||
|
fmt.Println(`TinyGo "net" package upgrade tool
|
||||||
|
|
||||||
|
Automates Step 1 & Step 2 of the README upgrade process:
|
||||||
|
Step 1: Backport differences from Go UPSTREAM to current CUR
|
||||||
|
Step 2: Generate comparison report of NEW vs UPSTREAM
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
upgrade [--dry-run] [--cur VERSION] [--upstream VERSION] [--file FILE]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
upgrade --dry-run # Preview what would change
|
||||||
|
upgrade --cur 1.21.4 --upstream 1.26.2 # Perform the upgrade
|
||||||
|
upgrade --dry-run --file dial.go # Preview single file`)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the TinyGo net directory (two levels up from tools/upgrade/)
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to working directory
|
||||||
|
exe, _ = os.Getwd()
|
||||||
|
} else {
|
||||||
|
exe = filepath.Dir(filepath.Dir(filepath.Dir(exe)))
|
||||||
|
}
|
||||||
|
// Also support running via "go run" — use working directory if exe path looks wrong
|
||||||
|
tinygoNetDir := exe
|
||||||
|
if !fileExists(filepath.Join(tinygoNetDir, "netdev.go")) {
|
||||||
|
tinygoNetDir, _ = os.Getwd()
|
||||||
|
}
|
||||||
|
if !fileExists(filepath.Join(tinygoNetDir, "netdev.go")) {
|
||||||
|
errorf("Cannot find TinyGo net directory. Run from the net package root or build the tool first.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := &upgrader{
|
||||||
|
curVersion: *curVersion,
|
||||||
|
upstreamVersion: *upstreamVersion,
|
||||||
|
dryRun: *dryRun,
|
||||||
|
tinygoNetDir: tinygoNetDir,
|
||||||
|
workDir: filepath.Join(tinygoNetDir, ".upgrade-work"),
|
||||||
|
reportDir: filepath.Join(tinygoNetDir, ".upgrade-report"),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||||
|
fmt.Println(" TinyGo net package upgrade")
|
||||||
|
fmt.Printf(" CUR: Go %s → UPSTREAM: Go %s\n", u.curVersion, u.upstreamVersion)
|
||||||
|
if u.dryRun {
|
||||||
|
fmt.Println(" Mode: DRY RUN (no files will be changed)")
|
||||||
|
} else {
|
||||||
|
fmt.Println(" Mode: APPLY CHANGES")
|
||||||
|
}
|
||||||
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Set up report directory
|
||||||
|
os.RemoveAll(u.reportDir)
|
||||||
|
os.MkdirAll(filepath.Join(u.reportDir, "diffs"), 0o755)
|
||||||
|
os.MkdirAll(filepath.Join(u.reportDir, "merged"), 0o755)
|
||||||
|
|
||||||
|
// Resolve Go source trees
|
||||||
|
info("Resolving Go %s source...", u.curVersion)
|
||||||
|
u.curRoot, err = resolveGoSource(u.curVersion, u.workDir)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to resolve Go %s: %v", u.curVersion, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
info(" → %s", u.curRoot)
|
||||||
|
|
||||||
|
info("Resolving Go %s source...", u.upstreamVersion)
|
||||||
|
u.upstreamRoot, err = resolveGoSource(u.upstreamVersion, u.workDir)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to resolve Go %s: %v", u.upstreamVersion, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
info(" → %s", u.upstreamRoot)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Build file list
|
||||||
|
var filesToProcess []string
|
||||||
|
if *singleFile != "" {
|
||||||
|
filesToProcess = []string{*singleFile}
|
||||||
|
} else {
|
||||||
|
filesToProcess = append(filesToProcess, unmodifiedFiles...)
|
||||||
|
filesToProcess = append(filesToProcess, modifiedFiles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate and sort for deterministic order
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var deduped []string
|
||||||
|
for _, f := range filesToProcess {
|
||||||
|
if !seen[f] {
|
||||||
|
seen[f] = true
|
||||||
|
deduped = append(deduped, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(deduped)
|
||||||
|
filesToProcess = deduped
|
||||||
|
|
||||||
|
total := len(filesToProcess)
|
||||||
|
for idx, file := range filesToProcess {
|
||||||
|
fmt.Printf(colorBlue+"[%d/%d]"+colorReset+" Processing %s...\n", idx+1, total, file)
|
||||||
|
|
||||||
|
// Skip TinyGo-only files
|
||||||
|
if tinygoOnlyFiles[file] {
|
||||||
|
info(" Skipping (TinyGo-only file)")
|
||||||
|
u.addSummary("SKIPPED_TINYGO_ONLY", file, "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create diff directory structure
|
||||||
|
os.MkdirAll(filepath.Dir(filepath.Join(u.reportDir, "diffs", file)), 0o755)
|
||||||
|
|
||||||
|
if modifiedFilesSet[file] {
|
||||||
|
u.processModified(file)
|
||||||
|
} else {
|
||||||
|
u.processUnmodified(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.writeSummaryFile()
|
||||||
|
u.printSummary()
|
||||||
|
}
|
||||||
-515
@@ -1,515 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# TinyGo "net" package upgrade script
|
|
||||||
#
|
|
||||||
# Automates Step 1 & Step 2 of the README upgrade process:
|
|
||||||
# Step 1: Backport differences from Go UPSTREAM to current CUR
|
|
||||||
# Step 2: Generate comparison report of NEW vs UPSTREAM
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./upgrade.sh [--dry-run] [--cur VERSION] [--upstream VERSION]
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# ./upgrade.sh --dry-run # Preview what would change
|
|
||||||
# ./upgrade.sh --cur 1.21.4 --upstream 1.26.2 # Perform the upgrade
|
|
||||||
# ./upgrade.sh --dry-run --file dial.go # Preview single file
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
|
||||||
CUR_VERSION="1.21.4"
|
|
||||||
UPSTREAM_VERSION="1.26.2"
|
|
||||||
DRY_RUN=false
|
|
||||||
SINGLE_FILE=""
|
|
||||||
TINYGO_NET_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
WORK_DIR="${TINYGO_NET_DIR}/.upgrade-work"
|
|
||||||
REPORT_DIR="${TINYGO_NET_DIR}/.upgrade-report"
|
|
||||||
|
|
||||||
# ── Parse arguments ─────────────────────────────────────────────────────────
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--dry-run) DRY_RUN=true; shift ;;
|
|
||||||
--cur) CUR_VERSION="$2"; shift 2 ;;
|
|
||||||
--upstream) UPSTREAM_VERSION="$2"; shift 2 ;;
|
|
||||||
--file) SINGLE_FILE="$2"; shift 2 ;;
|
|
||||||
--help|-h)
|
|
||||||
sed -n '3,/^$/p' "$0"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*) echo "Unknown option: $1"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── Color helpers ───────────────────────────────────────────────────────────
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
||||||
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
||||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
||||||
|
|
||||||
# ── File classification ─────────────────────────────────────────────────────
|
|
||||||
# Files entirely new to TinyGo (not from upstream) — never overwrite these.
|
|
||||||
TINYGO_ONLY_FILES=(
|
|
||||||
"netdev.go"
|
|
||||||
"tlssock.go"
|
|
||||||
"README.md"
|
|
||||||
"LICENSE"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Files that are straight copies from upstream (no TINYGO modifications).
|
|
||||||
# These can be replaced directly from upstream.
|
|
||||||
UNMODIFIED_FILES=(
|
|
||||||
"ip.go"
|
|
||||||
"mac.go"
|
|
||||||
"mac_test.go"
|
|
||||||
"parse.go"
|
|
||||||
"pipe.go"
|
|
||||||
"http/clone.go"
|
|
||||||
"http/cookie.go"
|
|
||||||
"http/fs.go"
|
|
||||||
"http/http.go"
|
|
||||||
"http/jar.go"
|
|
||||||
"http/method.go"
|
|
||||||
"http/sniff.go"
|
|
||||||
"http/status.go"
|
|
||||||
"http/internal/ascii/print.go"
|
|
||||||
"http/internal/ascii/print_test.go"
|
|
||||||
"http/internal/chunked.go"
|
|
||||||
"http/internal/chunked_test.go"
|
|
||||||
"http/httptest/recorder.go"
|
|
||||||
"http/httputil/dump.go"
|
|
||||||
"http/httputil/httputil.go"
|
|
||||||
"http/httputil/persist.go"
|
|
||||||
"http/httputil/reverseproxy.go"
|
|
||||||
"http/pprof/pprof.go"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Files copied AND modified for TinyGo (contain // TINYGO markers).
|
|
||||||
# These need 3-way merge: upstream changes applied to TinyGo-modified version.
|
|
||||||
MODIFIED_FILES=(
|
|
||||||
"dial.go"
|
|
||||||
"interface.go"
|
|
||||||
"iprawsock.go"
|
|
||||||
"ipsock.go"
|
|
||||||
"lookup.go"
|
|
||||||
"lookup_unix.go"
|
|
||||||
"lookup_windows.go"
|
|
||||||
"net.go"
|
|
||||||
"tcpsock.go"
|
|
||||||
"udpsock.go"
|
|
||||||
"unixsock.go"
|
|
||||||
"http/client.go"
|
|
||||||
"http/header.go"
|
|
||||||
"http/pattern.go"
|
|
||||||
"http/request.go"
|
|
||||||
"http/response.go"
|
|
||||||
"http/server.go"
|
|
||||||
"http/transfer.go"
|
|
||||||
"http/transport.go"
|
|
||||||
"http/httptest/httptest.go"
|
|
||||||
"http/httptest/server.go"
|
|
||||||
"http/httptrace/trace.go"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── Map TinyGo paths to upstream Go stdlib paths ───────────────────────────
|
|
||||||
# TinyGo net/ -> Go src/net/
|
|
||||||
# TinyGo http/ -> Go src/net/http/
|
|
||||||
upstream_path() {
|
|
||||||
local file="$1"
|
|
||||||
if [[ "$file" == http/* ]]; then
|
|
||||||
echo "src/net/${file}"
|
|
||||||
else
|
|
||||||
echo "src/net/${file}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Detect per-file CUR version from TINYGO header comment ─────────────────
|
|
||||||
detect_cur_version() {
|
|
||||||
local file="$1"
|
|
||||||
local header
|
|
||||||
header=$(head -1 "$file" 2>/dev/null || true)
|
|
||||||
if [[ "$header" =~ Go\ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then
|
|
||||||
echo "${BASH_REMATCH[1]}"
|
|
||||||
else
|
|
||||||
echo "$CUR_VERSION"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Download Go source tarball ──────────────────────────────────────────────
|
|
||||||
download_go_source() {
|
|
||||||
local version="$1"
|
|
||||||
local dest_dir="${WORK_DIR}/go${version}"
|
|
||||||
|
|
||||||
if [[ -d "$dest_dir/src/net" ]]; then
|
|
||||||
info "Go ${version} source already cached at ${dest_dir}" >&2
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local tarball="${WORK_DIR}/go${version}.src.tar.gz"
|
|
||||||
local url="https://go.dev/dl/go${version}.src.tar.gz"
|
|
||||||
|
|
||||||
mkdir -p "$WORK_DIR"
|
|
||||||
|
|
||||||
if [[ ! -f "$tarball" ]]; then
|
|
||||||
info "Downloading Go ${version} source from ${url} ..."
|
|
||||||
curl -fSL -o "$tarball" "$url"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Extracting Go ${version} source..."
|
|
||||||
mkdir -p "$dest_dir"
|
|
||||||
tar xzf "$tarball" -C "$dest_dir" --strip-components=1
|
|
||||||
success "Go ${version} source ready at ${dest_dir}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Try to use local gvm install first, fall back to download
|
|
||||||
resolve_go_source() {
|
|
||||||
local version="$1"
|
|
||||||
local gvm_path="${HOME}/.gvm/gos/go${version}"
|
|
||||||
|
|
||||||
if [[ -d "${gvm_path}/src/net" ]]; then
|
|
||||||
echo "$gvm_path"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check GOROOT if the active Go matches
|
|
||||||
local goroot
|
|
||||||
goroot="$(go env GOROOT 2>/dev/null || true)"
|
|
||||||
if [[ -n "$goroot" && -d "${goroot}/src/net" ]]; then
|
|
||||||
local active_ver
|
|
||||||
active_ver="$(go version | grep -oP '\d+\.\d+\.\d+')"
|
|
||||||
if [[ "$active_ver" == "$version" ]]; then
|
|
||||||
echo "$goroot"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
download_go_source "$version" >&2
|
|
||||||
echo "${WORK_DIR}/go${version}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Check if a file exists upstream ─────────────────────────────────────────
|
|
||||||
file_exists_upstream() {
|
|
||||||
local go_src_root="$1"
|
|
||||||
local file="$2"
|
|
||||||
local upath
|
|
||||||
upath="$(upstream_path "$file")"
|
|
||||||
[[ -f "${go_src_root}/${upath}" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Process unmodified (straight copy) files ────────────────────────────────
|
|
||||||
process_unmodified() {
|
|
||||||
local file="$1"
|
|
||||||
local upstream_root="$2"
|
|
||||||
local upath
|
|
||||||
upath="$(upstream_path "$file")"
|
|
||||||
local upstream_file="${upstream_root}/${upath}"
|
|
||||||
local tinygo_file="${TINYGO_NET_DIR}/${file}"
|
|
||||||
|
|
||||||
if [[ ! -f "$upstream_file" ]]; then
|
|
||||||
warn "MISSING in upstream ${UPSTREAM_VERSION}: ${upath}"
|
|
||||||
echo "MISSING_UPSTREAM ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if the file actually changed between versions
|
|
||||||
local cur_root="$3"
|
|
||||||
local cur_file="${cur_root}/${upath}"
|
|
||||||
|
|
||||||
if [[ -f "$cur_file" ]] && diff -q "$cur_file" "$upstream_file" >/dev/null 2>&1; then
|
|
||||||
echo "UNCHANGED ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if $DRY_RUN; then
|
|
||||||
info "[DRY-RUN] Would replace ${file} with upstream ${UPSTREAM_VERSION} copy"
|
|
||||||
if [[ -f "$cur_file" ]]; then
|
|
||||||
diff -u "$cur_file" "$upstream_file" > "${REPORT_DIR}/diffs/${file}.diff" 2>/dev/null || true
|
|
||||||
local lines
|
|
||||||
lines=$(wc -l < "${REPORT_DIR}/diffs/${file}.diff" 2>/dev/null | tr -d ' ' || echo "0")
|
|
||||||
info " Upstream diff: ${lines} lines (see .upgrade-report/diffs/${file}.diff)"
|
|
||||||
fi
|
|
||||||
echo "WOULD_COPY ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
else
|
|
||||||
# Update version header if it has one
|
|
||||||
local header
|
|
||||||
header=$(head -1 "$tinygo_file" 2>/dev/null || true)
|
|
||||||
if [[ "$header" =~ ^//\ TINYGO ]]; then
|
|
||||||
# Preserve TINYGO header, replace rest with upstream
|
|
||||||
local old_ver
|
|
||||||
old_ver=$(detect_cur_version "$tinygo_file")
|
|
||||||
head -1 "$tinygo_file" > "${tinygo_file}.tmp"
|
|
||||||
# Update version in header
|
|
||||||
sed -i "s/Go ${old_ver}/Go ${UPSTREAM_VERSION}/" "${tinygo_file}.tmp"
|
|
||||||
tail -n +2 "$upstream_file" >> "${tinygo_file}.tmp"
|
|
||||||
mv "${tinygo_file}.tmp" "$tinygo_file"
|
|
||||||
else
|
|
||||||
cp "$upstream_file" "$tinygo_file"
|
|
||||||
fi
|
|
||||||
echo "COPIED ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
success "Copied ${file} from upstream ${UPSTREAM_VERSION}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Process modified (3-way merge) files ────────────────────────────────────
|
|
||||||
process_modified() {
|
|
||||||
local file="$1"
|
|
||||||
local upstream_root="$2"
|
|
||||||
local cur_root="$3"
|
|
||||||
|
|
||||||
local upath
|
|
||||||
upath="$(upstream_path "$file")"
|
|
||||||
local upstream_file="${upstream_root}/${upath}"
|
|
||||||
local tinygo_file="${TINYGO_NET_DIR}/${file}"
|
|
||||||
|
|
||||||
if [[ ! -f "$upstream_file" ]]; then
|
|
||||||
warn "MISSING in upstream ${UPSTREAM_VERSION}: ${upath}"
|
|
||||||
echo "MISSING_UPSTREAM ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect the actual CUR version for this specific file
|
|
||||||
local file_cur_version
|
|
||||||
file_cur_version=$(detect_cur_version "$tinygo_file")
|
|
||||||
|
|
||||||
# Get the CUR source root for this specific file version
|
|
||||||
local file_cur_root
|
|
||||||
if [[ "$file_cur_version" != "$CUR_VERSION" ]]; then
|
|
||||||
info "${file}: based on Go ${file_cur_version} (not default ${CUR_VERSION})"
|
|
||||||
file_cur_root=$(resolve_go_source "$file_cur_version")
|
|
||||||
file_cur_root=$(echo "$file_cur_root" | tail -1)
|
|
||||||
else
|
|
||||||
file_cur_root="$cur_root"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local cur_file="${file_cur_root}/${upath}"
|
|
||||||
|
|
||||||
if [[ ! -f "$cur_file" ]]; then
|
|
||||||
warn "MISSING in CUR ${file_cur_version}: ${upath} — can only diff against current TinyGo version"
|
|
||||||
echo "MISSING_CUR ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
|
|
||||||
# Still generate a diff of upstream vs tinygo for manual review
|
|
||||||
diff -u "$tinygo_file" "$upstream_file" > "${REPORT_DIR}/diffs/${file}.upstream-vs-tinygo.diff" 2>/dev/null || true
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if upstream even changed this file
|
|
||||||
if diff -q "$cur_file" "$upstream_file" >/dev/null 2>&1; then
|
|
||||||
echo "UNCHANGED ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate upstream diff (what changed from CUR -> UPSTREAM in official Go)
|
|
||||||
local upstream_diff="${REPORT_DIR}/diffs/${file}.upstream-changes.diff"
|
|
||||||
diff -u "$cur_file" "$upstream_file" > "$upstream_diff" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Generate current tinygo diff (what TinyGo changed from CUR)
|
|
||||||
local tinygo_diff="${REPORT_DIR}/diffs/${file}.tinygo-changes.diff"
|
|
||||||
diff -u "$cur_file" "$tinygo_file" > "$tinygo_diff" 2>/dev/null || true
|
|
||||||
|
|
||||||
local upstream_lines
|
|
||||||
upstream_lines=$(wc -l < "$upstream_diff" 2>/dev/null | tr -d ' ' || echo "0")
|
|
||||||
|
|
||||||
if $DRY_RUN; then
|
|
||||||
info "[DRY-RUN] Would 3-way merge ${file} (Go ${file_cur_version} → ${UPSTREAM_VERSION})"
|
|
||||||
info " Upstream changes: ${upstream_lines} diff lines"
|
|
||||||
info " See: .upgrade-report/diffs/${file}.upstream-changes.diff"
|
|
||||||
info " See: .upgrade-report/diffs/${file}.tinygo-changes.diff"
|
|
||||||
echo "WOULD_MERGE ${file} (${upstream_lines} upstream diff lines)" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
else
|
|
||||||
# Attempt 3-way merge using diff3
|
|
||||||
# Base = CUR upstream, Ours = TinyGo modified, Theirs = new upstream
|
|
||||||
local merged="${REPORT_DIR}/merged/${file}"
|
|
||||||
mkdir -p "$(dirname "$merged")"
|
|
||||||
|
|
||||||
# diff3: merge changes from cur_file->upstream_file into tinygo_file
|
|
||||||
# -m for merge mode, using tinygo_file as "ours"
|
|
||||||
if diff3 -m "$tinygo_file" "$cur_file" "$upstream_file" > "$merged" 2>/dev/null; then
|
|
||||||
# Clean merge
|
|
||||||
cp "$merged" "$tinygo_file"
|
|
||||||
|
|
||||||
# Update version header
|
|
||||||
local old_ver
|
|
||||||
old_ver=$(detect_cur_version "$tinygo_file")
|
|
||||||
sed -i "1s/Go ${old_ver}/Go ${UPSTREAM_VERSION}/" "$tinygo_file"
|
|
||||||
|
|
||||||
echo "MERGED_CLEAN ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
success "Merged ${file} cleanly (Go ${file_cur_version} → ${UPSTREAM_VERSION})"
|
|
||||||
else
|
|
||||||
# Merge conflicts — save the conflicted file for manual resolution
|
|
||||||
cp "$merged" "${tinygo_file}.conflicted"
|
|
||||||
|
|
||||||
# Count conflicts
|
|
||||||
local conflicts
|
|
||||||
conflicts=$(grep -c '^<<<<<<<' "$merged" 2>/dev/null || echo "0")
|
|
||||||
|
|
||||||
echo "CONFLICTS ${file} (${conflicts} conflicts)" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
warn "CONFLICTS in ${file}: ${conflicts} conflict(s)"
|
|
||||||
warn " Conflicted merge saved to: ${file}.conflicted"
|
|
||||||
warn " Upstream changes: .upgrade-report/diffs/${file}.upstream-changes.diff"
|
|
||||||
warn " TinyGo changes: .upgrade-report/diffs/${file}.tinygo-changes.diff"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Main ────────────────────────────────────────────────────────────────────
|
|
||||||
main() {
|
|
||||||
echo ""
|
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
|
||||||
echo " TinyGo net package upgrade"
|
|
||||||
echo " CUR: Go ${CUR_VERSION} → UPSTREAM: Go ${UPSTREAM_VERSION}"
|
|
||||||
if $DRY_RUN; then
|
|
||||||
echo " Mode: DRY RUN (no files will be changed)"
|
|
||||||
else
|
|
||||||
echo " Mode: APPLY CHANGES"
|
|
||||||
fi
|
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Set up report directory
|
|
||||||
rm -rf "$REPORT_DIR"
|
|
||||||
mkdir -p "${REPORT_DIR}/diffs" "${REPORT_DIR}/merged"
|
|
||||||
|
|
||||||
# Resolve Go source trees
|
|
||||||
info "Resolving Go ${CUR_VERSION} source..."
|
|
||||||
local cur_root
|
|
||||||
cur_root=$(resolve_go_source "$CUR_VERSION")
|
|
||||||
cur_root=$(echo "$cur_root" | tail -1)
|
|
||||||
info " → ${cur_root}"
|
|
||||||
|
|
||||||
info "Resolving Go ${UPSTREAM_VERSION} source..."
|
|
||||||
local upstream_root
|
|
||||||
upstream_root=$(resolve_go_source "$UPSTREAM_VERSION")
|
|
||||||
upstream_root=$(echo "$upstream_root" | tail -1)
|
|
||||||
info " → ${upstream_root}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Build file list
|
|
||||||
local files_to_process=()
|
|
||||||
if [[ -n "$SINGLE_FILE" ]]; then
|
|
||||||
files_to_process=("$SINGLE_FILE")
|
|
||||||
else
|
|
||||||
files_to_process=("${UNMODIFIED_FILES[@]}" "${MODIFIED_FILES[@]}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Process each file
|
|
||||||
local total=${#files_to_process[@]}
|
|
||||||
local idx=0
|
|
||||||
|
|
||||||
for file in "${files_to_process[@]}"; do
|
|
||||||
idx=$((idx + 1))
|
|
||||||
echo -e "${BLUE}[${idx}/${total}]${NC} Processing ${file}..."
|
|
||||||
|
|
||||||
# Skip TinyGo-only files
|
|
||||||
local is_tinygo_only=false
|
|
||||||
for tgo_file in "${TINYGO_ONLY_FILES[@]}"; do
|
|
||||||
if [[ "$file" == "$tgo_file" ]]; then
|
|
||||||
is_tinygo_only=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if $is_tinygo_only; then
|
|
||||||
info " Skipping (TinyGo-only file)"
|
|
||||||
echo "SKIPPED_TINYGO_ONLY ${file}" >> "${REPORT_DIR}/summary.txt"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create diff directory structure
|
|
||||||
mkdir -p "$(dirname "${REPORT_DIR}/diffs/${file}")"
|
|
||||||
|
|
||||||
# Is it a modified or unmodified file?
|
|
||||||
local is_modified=false
|
|
||||||
for mod_file in "${MODIFIED_FILES[@]}"; do
|
|
||||||
if [[ "$file" == "$mod_file" ]]; then
|
|
||||||
is_modified=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if $is_modified; then
|
|
||||||
process_modified "$file" "$upstream_root" "$cur_root"
|
|
||||||
else
|
|
||||||
process_unmodified "$file" "$upstream_root" "$cur_root"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── Summary report ──────────────────────────────────────────────────────
|
|
||||||
echo ""
|
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
|
||||||
echo " Summary"
|
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
|
||||||
|
|
||||||
if [[ -f "${REPORT_DIR}/summary.txt" ]]; then
|
|
||||||
local unchanged merged_clean conflicts_count would_copy would_merge copied missing_upstream missing_cur
|
|
||||||
unchanged=$(grep -c "^UNCHANGED " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
merged_clean=$(grep -c "^MERGED_CLEAN " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
conflicts_count=$(grep -c "^CONFLICTS " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
would_copy=$(grep -c "^WOULD_COPY " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
would_merge=$(grep -c "^WOULD_MERGE " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
copied=$(grep -c "^COPIED " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
missing_upstream=$(grep -c "^MISSING_UPSTREAM " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
missing_cur=$(grep -c "^MISSING_CUR " "${REPORT_DIR}/summary.txt" || true)
|
|
||||||
# Default to 0 if empty
|
|
||||||
: "${unchanged:=0}" "${merged_clean:=0}" "${conflicts_count:=0}"
|
|
||||||
: "${would_copy:=0}" "${would_merge:=0}" "${copied:=0}"
|
|
||||||
: "${missing_upstream:=0}" "${missing_cur:=0}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Unchanged (no upstream changes): ${unchanged}"
|
|
||||||
if $DRY_RUN; then
|
|
||||||
echo " Would copy (unmodified): ${would_copy}"
|
|
||||||
echo " Would merge (modified): ${would_merge}"
|
|
||||||
else
|
|
||||||
echo " Copied (unmodified): ${copied}"
|
|
||||||
echo " Merged cleanly: ${merged_clean}"
|
|
||||||
echo " CONFLICTS (need manual fix): ${conflicts_count}"
|
|
||||||
fi
|
|
||||||
[[ "$missing_upstream" -gt 0 ]] && echo " Missing in upstream: ${missing_upstream}"
|
|
||||||
[[ "$missing_cur" -gt 0 ]] && echo " Missing in CUR baseline: ${missing_cur}"
|
|
||||||
|
|
||||||
if [[ "$conflicts_count" -gt 0 ]]; then
|
|
||||||
echo ""
|
|
||||||
warn "Files with merge conflicts:"
|
|
||||||
grep "^CONFLICTS " "${REPORT_DIR}/summary.txt" | while read -r _ f rest; do
|
|
||||||
echo " ${f} ${rest}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if $DRY_RUN && [[ "$would_merge" -gt 0 ]]; then
|
|
||||||
echo ""
|
|
||||||
info "Modified files that need merging:"
|
|
||||||
grep "^WOULD_MERGE " "${REPORT_DIR}/summary.txt" | while read -r _ f rest; do
|
|
||||||
echo " ${f} ${rest}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Full report: ${REPORT_DIR}/"
|
|
||||||
echo " Diffs: ${REPORT_DIR}/diffs/"
|
|
||||||
if ! $DRY_RUN; then
|
|
||||||
echo " Merged: ${REPORT_DIR}/merged/"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if $DRY_RUN; then
|
|
||||||
info "Dry run complete. Review the report, then run without --dry-run to apply."
|
|
||||||
else
|
|
||||||
info "Upgrade applied. Review changes, resolve any conflicts, then test."
|
|
||||||
echo ""
|
|
||||||
echo "Next steps:"
|
|
||||||
echo " 1. Review and resolve any .conflicted files"
|
|
||||||
echo " 2. Check for files missing from upstream (may have been renamed/removed)"
|
|
||||||
echo " 3. Verify TINYGO comments are preserved"
|
|
||||||
echo " 4. Test with TinyGo example/net examples"
|
|
||||||
echo " 5. Update README.md version references"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
Reference in New Issue
Block a user