From 383ca96da71a9301d40f4d622532bece16519caf Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 16 Dec 2025 14:21:39 -0500 Subject: [PATCH 1/2] Automate `swift package resolve` On upgrade of a Swift plugin (with a `Package.resolved` file), clone the git repo, re-run `swift package resolve`, and copy the `Package.resolved` file to the directory. Basically, an automation of the steps described in #2161. Also, bump golangci-lint to latest. Fixes #1941. --- Makefile | 2 +- internal/cmd/fetcher/main.go | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 84f05e17..3a7d0314 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ DOCKER_BUILD_EXTRA_ARGS ?= DOCKER_BUILDER := bufbuild-plugins DOCKER_CACHE_DIR ?= $(TMP)/dockercache GO ?= go -GOLANGCI_LINT_VERSION ?= v2.6.1 +GOLANGCI_LINT_VERSION ?= v2.7.2 GOLANGCI_LINT := $(TMP)/golangci-lint-$(GOLANGCI_LINT_VERSION) GO_TEST_FLAGS ?= -race -count=1 diff --git a/internal/cmd/fetcher/main.go b/internal/cmd/fetcher/main.go index b9e910cd..54bace61 100644 --- a/internal/cmd/fetcher/main.go +++ b/internal/cmd/fetcher/main.go @@ -67,6 +67,9 @@ func postProcessCreatedPlugins(ctx context.Context, plugins []createdPlugin) err if err := recreateNPMPackageLock(ctx, plugin); err != nil { return fmt.Errorf("failed to recreate package-lock.json for %s: %w", newPluginRef, err) } + if err := recreateSwiftPackageResolved(ctx, plugin); err != nil { + return fmt.Errorf("failed to resolve Swift package for %s: %w", newPluginRef, err) + } } if err := runPluginTests(ctx, plugins); err != nil { return fmt.Errorf("failed to run plugin tests: %w", err) @@ -119,6 +122,97 @@ func recreateNPMPackageLock(ctx context.Context, plugin createdPlugin) error { return cmd.Run() } +// recreateSwiftPackageResolved resolves Swift package dependencies for plugins that use Swift packages. +// It clones the git repository specified in the Dockerfile, runs 'swift package resolve', +// and moves the generated Package.resolved file to the version directory. +func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) error { + versionDir := filepath.Join(plugin.pluginDir, plugin.newVersion) + packageResolved := filepath.Join(versionDir, "Package.resolved") + _, err := os.Stat(packageResolved) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + // no Package.resolved to update + return nil + } + + // Read the Dockerfile to find the git clone command + dockerfile := filepath.Join(versionDir, "Dockerfile") + file, err := os.Open(dockerfile) + if err != nil { + return fmt.Errorf("failed to open Dockerfile: %w", err) + } + defer file.Close() + + var gitCloneCmd string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "RUN git clone") { + // Strip the "RUN " prefix + gitCloneCmd = strings.TrimSpace(strings.TrimPrefix(line, "RUN ")) + break + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to read Dockerfile: %w", err) + } + if gitCloneCmd == "" { + return errors.New("no 'RUN git clone' command found in Dockerfile") + } + + log.Printf("resolving Swift package for %s/%s:%s", plugin.org, plugin.name, plugin.newVersion) + + // Execute the git clone command + cmd := exec.CommandContext(ctx, "sh", "-c", gitCloneCmd) + cmd.Dir = versionDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run git clone: %w", err) + } + + // Extract the repository name from the git clone command to determine the directory + parts := strings.Fields(gitCloneCmd) + var repoDir string + for _, part := range parts { + if strings.HasPrefix(part, "https://") { + // Extract directory name from URL (e.g., "repo.git" or "repo") + repoName := filepath.Base(part) + repoName = strings.TrimSuffix(repoName, ".git") + repoDir = filepath.Join(versionDir, repoName) + break + } + } + if repoDir == "" { + return errors.New("failed to determine repository directory from git clone command") + } + + // Run `swift package resolve` in the cloned directory + cmd = exec.CommandContext(ctx, "swift", "package", "resolve") + cmd.Dir = repoDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run swift package resolve: %w", err) + } + + // Move the Package.resolved file from the cloned directory to the version directory + src := filepath.Join(repoDir, "Package.resolved") + dest := packageResolved + if err := os.Rename(src, dest); err != nil { + return fmt.Errorf("failed to move Package.resolved: %w", err) + } + + // Remove the cloned repo + if err := os.RemoveAll(repoDir); err != nil { + return fmt.Errorf("removing cloned repo: %w", err) + } + + return nil +} + // runPluginTests runs 'make test PLUGINS="org/name:v"' in order to generate plugin.sum files. func runPluginTests(ctx context.Context, plugins []createdPlugin) error { pluginsEnv := make([]string, 0, len(plugins)) From f699d194e3b343a82c254f49271189eac559e072 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Tue, 16 Dec 2025 14:51:12 -0500 Subject: [PATCH 2/2] Switch to creating a tempdir and specifying that for cloning --- internal/cmd/fetcher/main.go | 40 +++++++++++++----------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/internal/cmd/fetcher/main.go b/internal/cmd/fetcher/main.go index 54bace61..05739943 100644 --- a/internal/cmd/fetcher/main.go +++ b/internal/cmd/fetcher/main.go @@ -125,7 +125,7 @@ func recreateNPMPackageLock(ctx context.Context, plugin createdPlugin) error { // recreateSwiftPackageResolved resolves Swift package dependencies for plugins that use Swift packages. // It clones the git repository specified in the Dockerfile, runs 'swift package resolve', // and moves the generated Package.resolved file to the version directory. -func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) error { +func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) (retErr error) { versionDir := filepath.Join(plugin.pluginDir, plugin.newVersion) packageResolved := filepath.Join(versionDir, "Package.resolved") _, err := os.Stat(packageResolved) @@ -164,8 +164,17 @@ func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) err log.Printf("resolving Swift package for %s/%s:%s", plugin.org, plugin.name, plugin.newVersion) - // Execute the git clone command - cmd := exec.CommandContext(ctx, "sh", "-c", gitCloneCmd) + // Create a tempdir for cloning the repo + tmpDir, err := os.MkdirTemp("", "swift-repo-*") + if err != nil { + return fmt.Errorf("creating tmp dir: %w", err) + } + defer func() { + retErr = errors.Join(retErr, os.RemoveAll(tmpDir)) + }() + + // Execute the git clone command, cloning to the tmpDir + cmd := exec.CommandContext(ctx, "sh", "-c", gitCloneCmd, "--", tmpDir) cmd.Dir = versionDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -173,25 +182,9 @@ func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) err return fmt.Errorf("failed to run git clone: %w", err) } - // Extract the repository name from the git clone command to determine the directory - parts := strings.Fields(gitCloneCmd) - var repoDir string - for _, part := range parts { - if strings.HasPrefix(part, "https://") { - // Extract directory name from URL (e.g., "repo.git" or "repo") - repoName := filepath.Base(part) - repoName = strings.TrimSuffix(repoName, ".git") - repoDir = filepath.Join(versionDir, repoName) - break - } - } - if repoDir == "" { - return errors.New("failed to determine repository directory from git clone command") - } - // Run `swift package resolve` in the cloned directory cmd = exec.CommandContext(ctx, "swift", "package", "resolve") - cmd.Dir = repoDir + cmd.Dir = tmpDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { @@ -199,17 +192,12 @@ func recreateSwiftPackageResolved(ctx context.Context, plugin createdPlugin) err } // Move the Package.resolved file from the cloned directory to the version directory - src := filepath.Join(repoDir, "Package.resolved") + src := filepath.Join(tmpDir, "Package.resolved") dest := packageResolved if err := os.Rename(src, dest); err != nil { return fmt.Errorf("failed to move Package.resolved: %w", err) } - // Remove the cloned repo - if err := os.RemoveAll(repoDir); err != nil { - return fmt.Errorf("removing cloned repo: %w", err) - } - return nil }