Skip to content

Commit b104d00

Browse files
committed
Add detailed NuGet restore binlog archiving
1 parent 1fc8d90 commit b104d00

2 files changed

Lines changed: 171 additions & 1 deletion

File tree

module/nuget/nuget_cmd_build.go

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/xml"
1010
"errors"
1111
"fmt"
12+
"hash/fnv"
1213
"io"
1314
"net/http"
1415
neturl "net/url"
@@ -38,6 +39,8 @@ const (
3839
nugetBuildMaxTimeout = 6 * time.Hour
3940
nugetCommandIdleTimeout = 30 * time.Second
4041
nugetPreflightTimeout = 8 * time.Second
42+
nugetRestoreBinlogDir = ".murphysec"
43+
nugetTmpBinlogDir = "murphysec-nuget-binlogs"
4144
)
4245

4346
type nugetConfigXML struct {
@@ -453,8 +456,77 @@ func startCommandIdleWatchdog(
453456
}
454457
}
455458

459+
func nugetRestoreBinlogName(solutionPath string) string {
460+
base := filepath.Base(solutionPath)
461+
ext := filepath.Ext(base)
462+
name := strings.TrimSuffix(base, ext)
463+
if name == "" {
464+
name = "restore"
465+
}
466+
return name + ".restore.binlog"
467+
}
468+
469+
func nugetRestoreBinlogPath(solutionPath string) string {
470+
return filepath.Join(filepath.Dir(solutionPath), nugetRestoreBinlogDir, nugetRestoreBinlogName(solutionPath))
471+
}
472+
473+
func nugetRestoreTmpBinlogPath(solutionPath string) string {
474+
hasher := fnv.New32a()
475+
_, _ = hasher.Write([]byte(filepath.Clean(solutionPath)))
476+
return filepath.Join(
477+
os.TempDir(),
478+
nugetTmpBinlogDir,
479+
fmt.Sprintf("%08x", hasher.Sum32()),
480+
nugetRestoreBinlogName(solutionPath),
481+
)
482+
}
483+
484+
func copyFile(srcPath, dstPath string) error {
485+
src, err := os.Open(srcPath)
486+
if err != nil {
487+
return err
488+
}
489+
defer src.Close()
490+
491+
if err = os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
492+
return err
493+
}
494+
dst, err := os.Create(dstPath)
495+
if err != nil {
496+
return err
497+
}
498+
defer dst.Close()
499+
500+
if _, err = io.Copy(dst, src); err != nil {
501+
return err
502+
}
503+
return dst.Close()
504+
}
505+
506+
func archiveNugetRestoreBinlog(logger *zap.Logger, solutionPath string) (string, error) {
507+
srcPath := nugetRestoreBinlogPath(solutionPath)
508+
if _, err := os.Stat(srcPath); err != nil {
509+
return "", err
510+
}
511+
dstPath := nugetRestoreTmpBinlogPath(solutionPath)
512+
if err := copyFile(srcPath, dstPath); err != nil {
513+
return "", err
514+
}
515+
if logger != nil {
516+
logger.Sugar().Infof("NuGet restore binary log copied to tmp: %s", dstPath)
517+
}
518+
return dstPath, nil
519+
}
520+
456521
func dotnetRestoreArgs(solutionPath string) []string {
457-
args := []string{"restore", solutionPath, "-v", "detailed"}
522+
binlogPath := nugetRestoreBinlogPath(solutionPath)
523+
args := []string{
524+
"restore",
525+
solutionPath,
526+
"-v",
527+
"detailed",
528+
fmt.Sprintf("/bl:%s;ProjectImports=Embed", binlogPath),
529+
}
458530
if runtime.GOOS == "linux" {
459531
args = append(args, "-p:EnableWindowsTargeting=true")
460532
}
@@ -468,7 +540,14 @@ func dotnetListPackageArgs(solutionPath string) []string {
468540
// 通过先运行 dotnet restore 命令,确保项目中的所有 NuGet 包依赖项被正确恢复
469541
func buildPackage(ctx context.Context, logger *zap.Logger, solutionPath string) (err error) {
470542
//dotnet restore
543+
binlogPath := nugetRestoreBinlogPath(solutionPath)
544+
if err = os.MkdirAll(filepath.Dir(binlogPath), 0o755); err != nil {
545+
err = fmt.Errorf("create nuget binlog dir failed: %w", err)
546+
logger.Error(err.Error())
547+
return
548+
}
471549
args := dotnetRestoreArgs(solutionPath)
550+
logger.Sugar().Infof("NuGet restore binary log enabled: %s", binlogPath)
472551
if runtime.GOOS == "linux" {
473552
logger.Info("dotnet restore adds EnableWindowsTargeting for Linux compatibility")
474553
}
@@ -556,6 +635,11 @@ func buildPackage(ctx context.Context, logger *zap.Logger, solutionPath string)
556635
wg.Wait()
557636
close(done)
558637
err = cmd.Wait()
638+
if archivePath, archiveErr := archiveNugetRestoreBinlog(logger, solutionPath); archiveErr != nil {
639+
logger.Sugar().Warnf("copy NuGet restore binary log to tmp failed: src=%s err=%v", binlogPath, archiveErr)
640+
} else {
641+
logger.Sugar().Infof("NuGet restore binary log archive ready: %s", archivePath)
642+
}
559643
if err != nil {
560644
if idleTimeoutExceeded.Load() {
561645
return fmt.Errorf("dotnet restore idle timed out after %s without new output: %w\nstderr:\n%s\nstdout:\n%s",
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package nuget
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"runtime"
7+
"strings"
8+
"testing"
9+
)
10+
11+
func TestNugetRestoreBinlogPath(t *testing.T) {
12+
root := t.TempDir()
13+
solutionPath := filepath.Join(root, "src", "App.sln")
14+
15+
got := nugetRestoreBinlogPath(solutionPath)
16+
want := filepath.Join(root, "src", nugetRestoreBinlogDir, "App.restore.binlog")
17+
if got != want {
18+
t.Fatalf("unexpected binlog path: got %s want %s", got, want)
19+
}
20+
}
21+
22+
func TestNugetRestoreTmpBinlogPath(t *testing.T) {
23+
root := t.TempDir()
24+
solutionPath := filepath.Join(root, "src", "App.sln")
25+
26+
got := nugetRestoreTmpBinlogPath(solutionPath)
27+
if !strings.HasPrefix(got, filepath.Join(os.TempDir(), nugetTmpBinlogDir)+string(filepath.Separator)) {
28+
t.Fatalf("unexpected tmp binlog root: %s", got)
29+
}
30+
if !strings.HasSuffix(got, filepath.Join("App.restore.binlog")) {
31+
t.Fatalf("unexpected tmp binlog filename: %s", got)
32+
}
33+
}
34+
35+
func TestDotnetRestoreArgsEnableDetailedBinlog(t *testing.T) {
36+
root := t.TempDir()
37+
solutionPath := filepath.Join(root, "src", "App.csproj")
38+
39+
args := dotnetRestoreArgs(solutionPath)
40+
if len(args) < 5 {
41+
t.Fatalf("unexpected restore args length: %v", args)
42+
}
43+
if args[0] != "restore" || args[1] != solutionPath {
44+
t.Fatalf("unexpected restore command prefix: %v", args)
45+
}
46+
if args[2] != "-v" || args[3] != "detailed" {
47+
t.Fatalf("expected detailed verbosity, got %v", args)
48+
}
49+
if !strings.Contains(args[4], "/bl:") {
50+
t.Fatalf("expected binary log argument, got %s", args[4])
51+
}
52+
if !strings.Contains(args[4], "ProjectImports=Embed") {
53+
t.Fatalf("expected embedded project imports in binlog, got %s", args[4])
54+
}
55+
if !strings.Contains(args[4], "App.restore.binlog") {
56+
t.Fatalf("expected restore binlog filename, got %s", args[4])
57+
}
58+
if runtime.GOOS == "linux" && !strings.Contains(strings.Join(args, " "), "EnableWindowsTargeting=true") {
59+
t.Fatalf("expected linux restore args to include EnableWindowsTargeting=true, got %v", args)
60+
}
61+
}
62+
63+
func TestArchiveNugetRestoreBinlog(t *testing.T) {
64+
root := t.TempDir()
65+
solutionPath := filepath.Join(root, "src", "App.csproj")
66+
srcPath := nugetRestoreBinlogPath(solutionPath)
67+
if err := os.MkdirAll(filepath.Dir(srcPath), 0o755); err != nil {
68+
t.Fatalf("mkdir failed: %v", err)
69+
}
70+
wantContent := []byte("binlog")
71+
if err := os.WriteFile(srcPath, wantContent, 0o644); err != nil {
72+
t.Fatalf("write source binlog failed: %v", err)
73+
}
74+
75+
got, err := archiveNugetRestoreBinlog(nil, solutionPath)
76+
if err != nil {
77+
t.Fatalf("archive binlog failed: %v", err)
78+
}
79+
data, err := os.ReadFile(got)
80+
if err != nil {
81+
t.Fatalf("read archived binlog failed: %v", err)
82+
}
83+
if string(data) != string(wantContent) {
84+
t.Fatalf("unexpected archived content: got %q want %q", string(data), string(wantContent))
85+
}
86+
}

0 commit comments

Comments
 (0)