mirror of
https://gitea.com/gitea/act_runner
synced 2026-11-03 10:57:22 +01:00
Adds a weekly and on-demand workflow that runs the cross-compiled tests on Windows Server 2025 Core in a [dockur/windows](https://github.com/dockur/windows) QEMU VM, and fixes the Windows bugs it found in host and UNC paths, env handling, shell quoting, post-task scripts, health checks, checkout, action symlinks, step summaries, Git client certificates and docker mode from a Windows host. Host-mode steps now emit UTF-8, `bash` falls back to Git's bash instead of the WSL launcher, the runner can run as a native Windows service, steps can keep background helpers with `RUNNER_TRACKING_ID`, and Windows arm64 binaries are released. Fixes: https://gitea.com/gitea/runner/issues/1339 Fixes: https://gitea.com/gitea/runner/issues/637 Fixes: https://gitea.com/gitea/runner/issues/1172 Fixes: https://gitea.com/gitea/runner/issues/347 Reviewed-on: https://gitea.com/gitea/runner/pulls/1348 Reviewed-by: bircni <bircni@icloud.com>
171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
// Copyright 2024 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package filecollector
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/common/git"
|
|
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
type Handler interface {
|
|
WriteFile(path string, fi fs.FileInfo, linkName string, f io.Reader) error
|
|
}
|
|
|
|
type TarCollector struct {
|
|
TarWriter *tar.Writer
|
|
UID int
|
|
GID int
|
|
DstDir string
|
|
Executable bool // Windows has no exec bit, so mark everything executable like docker build
|
|
}
|
|
|
|
func (tc TarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
|
|
// create a new dir/file header
|
|
header, err := tar.FileInfoHeader(fi, filepath.ToSlash(linkName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// update the name to correctly reflect the desired destination when untaring
|
|
header.Name = path.Join(tc.DstDir, fpath)
|
|
if tc.Executable {
|
|
header.Mode = header.Mode&0o755 | 0o111
|
|
}
|
|
header.Uid = tc.UID
|
|
header.Gid = tc.GID
|
|
|
|
// write the header
|
|
if err := tc.TarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
// this is a symlink no reader provided
|
|
if f == nil {
|
|
return nil
|
|
}
|
|
|
|
// copy file data into tar writer
|
|
_, err = io.Copy(tc.TarWriter, f)
|
|
return err
|
|
}
|
|
|
|
type CopyCollector struct {
|
|
DstDir string
|
|
}
|
|
|
|
func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
|
|
fdestpath := filepath.Join(cc.DstDir, fpath)
|
|
if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
|
|
return err
|
|
}
|
|
// Remove any existing destination so we can overwrite read-only files
|
|
// (e.g. git pack files at mode 0444 trip EACCES on macOS and "Access is
|
|
// denied" on Windows when reopened with O_WRONLY) and so os.Symlink does
|
|
// not fail with EEXIST. os.Remove clears the Windows read-only attribute
|
|
// internally; on Unix unlink only needs write permission on the parent.
|
|
_ = os.Remove(fdestpath)
|
|
if f == nil {
|
|
return os.Symlink(linkName, fdestpath)
|
|
}
|
|
df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fi.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer df.Close()
|
|
_, err = io.Copy(df, f)
|
|
return err
|
|
}
|
|
|
|
type FileCollector struct {
|
|
SrcPath string
|
|
SrcPrefix string
|
|
SkipGitDir bool
|
|
UseGitIgnore bool
|
|
Handler Handler
|
|
}
|
|
|
|
// foldPath compares git's spelling of a path with the filesystem's, which may differ in case or Unicode normalization.
|
|
func foldPath(name string) string {
|
|
return strings.ToLower(norm.NFC.String(name))
|
|
}
|
|
|
|
func (fc *FileCollector) gitFiles(ctx context.Context) map[string]bool {
|
|
if !fc.UseGitIgnore {
|
|
return nil
|
|
}
|
|
files, err := git.ListFiles(ctx, fc.SrcPath)
|
|
if err != nil {
|
|
common.Logger(ctx).Debugf("Copying %s without applying .gitignore: %v", fc.SrcPath, err)
|
|
return nil
|
|
}
|
|
keep := map[string]bool{".": true}
|
|
for _, file := range files {
|
|
for file = foldPath(file); !keep[file]; file = path.Dir(file) {
|
|
keep[file] = true
|
|
}
|
|
}
|
|
return keep
|
|
}
|
|
|
|
func (fc *FileCollector) CollectFiles(ctx context.Context) filepath.WalkFunc {
|
|
keep := fc.gitFiles(ctx)
|
|
return func(file string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ctx.Err() != nil {
|
|
return errors.New("copy cancelled")
|
|
}
|
|
if fc.SkipGitDir && fi.Name() == ".git" {
|
|
if fi.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if keep != nil {
|
|
rel, _ := filepath.Rel(fc.SrcPath, file)
|
|
if rel = filepath.ToSlash(rel); !keep[foldPath(rel)] && !strings.Contains("/"+rel+"/", "/.git/") {
|
|
if fi.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
path := filepath.ToSlash(strings.TrimPrefix(file, fc.SrcPrefix))
|
|
|
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
linkName, err := os.Readlink(file)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to readlink '%s': %w", file, err)
|
|
}
|
|
return fc.Handler.WriteFile(path, fi, linkName, nil)
|
|
} else if !fi.Mode().IsRegular() {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
defer context.AfterFunc(ctx, func() { _ = f.Close() })()
|
|
|
|
return fc.Handler.WriteFile(path, fi, "", f)
|
|
}
|
|
}
|