133 lines
3.1 KiB
Go
133 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func regular(path string) bool { s, err := os.Stat(path); return err == nil && s.Mode().IsRegular() }
|
|
func directory(path string) bool { s, err := os.Stat(path); return err == nil && s.IsDir() }
|
|
|
|
func removeBuildOutput(root, path string) error {
|
|
base, err := filepath.EvalSymlinks(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := os.Lstat(path); errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
resolved, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(base, resolved)
|
|
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
return fmt.Errorf("refusing to remove output outside .build: %s", path)
|
|
}
|
|
return os.RemoveAll(path)
|
|
}
|
|
|
|
func copyFile(source, destination string) error {
|
|
in, err := os.Open(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = in.Close() }()
|
|
info, err := in.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return fmt.Errorf("not a regular file: %s", source)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
|
|
return err
|
|
}
|
|
out, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode().Perm())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, writeErr := io.Copy(out, in)
|
|
return errors.Join(writeErr, out.Close())
|
|
}
|
|
|
|
func copyTree(source, destination string) error {
|
|
return filepath.WalkDir(source, func(path string, entry fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(source, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out := filepath.Join(destination, rel)
|
|
if entry.IsDir() {
|
|
return os.MkdirAll(out, 0755)
|
|
}
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
return fmt.Errorf("unexpected seed symlink: %s", path)
|
|
}
|
|
return copyFile(path, out)
|
|
})
|
|
}
|
|
|
|
func archiveDirectory(source, destination string) error {
|
|
file, err := os.CreateTemp(filepath.Dir(destination), ".bd2-archive-*.tmp")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
temp := file.Name()
|
|
defer func() { _ = os.Remove(temp) }()
|
|
archive := zip.NewWriter(file)
|
|
err = filepath.WalkDir(source, func(path string, entry fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
return fmt.Errorf("unexpected package symlink: %s", path)
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header, err := zip.FileInfoHeader(info)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(filepath.Dir(source), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header.Name = filepath.ToSlash(rel)
|
|
if entry.IsDir() {
|
|
header.Name += "/"
|
|
_, err = archive.CreateHeader(header)
|
|
return err
|
|
}
|
|
header.Method = zip.Deflate
|
|
writer, err := archive.CreateHeader(header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, writeErr := io.Copy(writer, in)
|
|
return errors.Join(writeErr, in.Close())
|
|
})
|
|
err = errors.Join(err, archive.Close(), file.Close())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(temp, destination)
|
|
}
|