Skip to content

Commit f3e9ffd

Browse files
authored
feat: auto use uv --frozen when there is a uv.lock file (#663)
1 parent 0f75a4e commit f3e9ffd

3 files changed

Lines changed: 83 additions & 16 deletions

File tree

internal/core/local_runtime/dependency_installation_test.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func TestPrepareSyncArgs(t *testing.T) {
121121
appConfig: &app.Config{},
122122
}
123123

124-
args := runtime.prepareSyncArgs()
124+
args := runtime.prepareSyncArgs(false)
125125
require.Equal(t, []string{"sync", "--no-dev"}, args)
126126
})
127127

@@ -132,7 +132,7 @@ func TestPrepareSyncArgs(t *testing.T) {
132132
},
133133
}
134134

135-
args := runtime.prepareSyncArgs()
135+
args := runtime.prepareSyncArgs(false)
136136
require.Equal(t, []string{"sync", "--no-dev", "-i", "https://pypi.tuna.tsinghua.edu.cn/simple"}, args)
137137
})
138138

@@ -143,7 +143,7 @@ func TestPrepareSyncArgs(t *testing.T) {
143143
},
144144
}
145145

146-
args := runtime.prepareSyncArgs()
146+
args := runtime.prepareSyncArgs(false)
147147
require.Equal(t, []string{"sync", "--no-dev", "-v"}, args)
148148
})
149149

@@ -154,7 +154,7 @@ func TestPrepareSyncArgs(t *testing.T) {
154154
},
155155
}
156156

157-
args := runtime.prepareSyncArgs()
157+
args := runtime.prepareSyncArgs(false)
158158
require.Equal(t, []string{"sync", "--no-dev", "--no-cache", "--retries", "3"}, args)
159159
})
160160

@@ -167,7 +167,7 @@ func TestPrepareSyncArgs(t *testing.T) {
167167
},
168168
}
169169

170-
args := runtime.prepareSyncArgs()
170+
args := runtime.prepareSyncArgs(false)
171171
require.Equal(t, []string{
172172
"sync",
173173
"--no-dev",
@@ -176,6 +176,37 @@ func TestPrepareSyncArgs(t *testing.T) {
176176
"--no-cache",
177177
}, args)
178178
})
179+
180+
t.Run("sync args with uv.lock adds --frozen", func(t *testing.T) {
181+
runtime := &LocalPluginRuntime{
182+
appConfig: &app.Config{},
183+
}
184+
185+
args := runtime.prepareSyncArgs(true)
186+
require.Equal(t, []string{"sync", "--no-dev", "--frozen"}, args)
187+
})
188+
189+
t.Run("sync args with uv.lock deduplicates --frozen from extra args", func(t *testing.T) {
190+
runtime := &LocalPluginRuntime{
191+
appConfig: &app.Config{
192+
PipExtraArgs: "--frozen --no-cache",
193+
},
194+
}
195+
196+
args := runtime.prepareSyncArgs(true)
197+
require.Equal(t, []string{"sync", "--no-dev", "--frozen", "--no-cache"}, args)
198+
})
199+
200+
t.Run("sync args without uv.lock keeps --frozen from extra args", func(t *testing.T) {
201+
runtime := &LocalPluginRuntime{
202+
appConfig: &app.Config{
203+
PipExtraArgs: "--frozen --no-cache",
204+
},
205+
}
206+
207+
args := runtime.prepareSyncArgs(false)
208+
require.Equal(t, []string{"sync", "--no-dev", "--frozen", "--no-cache"}, args)
209+
})
179210
}
180211

181212
func TestPreparePipArgs(t *testing.T) {

internal/core/local_runtime/setup_python_environment.go

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,20 @@ func (p *LocalPluginRuntime) preparePipArgs() []string {
8585
args = append(args, "-vvv")
8686
}
8787

88-
if p.appConfig.PipExtraArgs != "" {
89-
extraArgs := strings.Split(p.appConfig.PipExtraArgs, " ")
90-
args = append(args, extraArgs...)
91-
}
88+
args = append(args, p.parseExtraArgs()...)
9289

9390
args = append([]string{"pip"}, args...)
9491

9592
return args
9693
}
9794

98-
func (p *LocalPluginRuntime) prepareSyncArgs() []string {
95+
func (p *LocalPluginRuntime) prepareSyncArgs(hasUvLock bool) []string {
9996
args := []string{"sync", "--no-dev"}
10097

98+
if hasUvLock {
99+
args = append(args, "--frozen")
100+
}
101+
101102
if p.appConfig.PipMirrorUrl != "" {
102103
args = append(args, "-i", p.appConfig.PipMirrorUrl)
103104
}
@@ -106,14 +107,31 @@ func (p *LocalPluginRuntime) prepareSyncArgs() []string {
106107
args = append(args, "-v")
107108
}
108109

109-
if p.appConfig.PipExtraArgs != "" {
110-
extraArgs := strings.Split(p.appConfig.PipExtraArgs, " ")
111-
args = append(args, extraArgs...)
110+
extraArgs := p.parseExtraArgs()
111+
if hasUvLock {
112+
extraArgs = p.deduplicateArgs(extraArgs, "--frozen")
112113
}
113-
114+
args = append(args, extraArgs...)
114115
return args
115116
}
116117

118+
func (p *LocalPluginRuntime) parseExtraArgs() []string {
119+
if p.appConfig.PipExtraArgs == "" {
120+
return nil
121+
}
122+
return strings.Fields(p.appConfig.PipExtraArgs)
123+
}
124+
125+
func (p *LocalPluginRuntime) deduplicateArgs(args []string, exclude string) []string {
126+
var result []string
127+
for _, arg := range args {
128+
if arg != exclude {
129+
result = append(result, arg)
130+
}
131+
}
132+
return result
133+
}
134+
117135
func (p *LocalPluginRuntime) detectDependencyFileType() (PythonDependencyFileType, error) {
118136
_, span := p.startSpan("python.detect_dependency_file")
119137
defer span.End()
@@ -143,12 +161,18 @@ func (p *LocalPluginRuntime) installDependencies(
143161
var args []string
144162
switch dependencyFileType {
145163
case pyprojectTomlFile:
146-
args = p.prepareSyncArgs()
164+
uvLockPath := path.Join(p.State.WorkingPath, "uv.lock")
165+
hasUvLock := false
166+
if _, err := os.Stat(uvLockPath); err == nil {
167+
hasUvLock = true
168+
}
169+
args = p.prepareSyncArgs(hasUvLock)
147170
parent.SetAttributes(
148171
attribute.String("python.install.method", "uv sync"),
149172
attribute.String("python.install.file", string(pyprojectTomlFile)),
173+
attribute.Bool("python.install.frozen", hasUvLock),
150174
)
151-
log.Info("installing plugin dependencies", "plugin", p.Config.Identity(), "method", "uv sync", "file", pyprojectTomlFile)
175+
log.Info("installing plugin dependencies", "plugin", p.Config.Identity(), "method", "uv sync", "file", pyprojectTomlFile, "frozen", hasUvLock)
152176
case requirementsTxtFile:
153177
args = p.preparePipArgs()
154178
parent.SetAttributes(
@@ -160,6 +184,8 @@ func (p *LocalPluginRuntime) installDependencies(
160184
return fmt.Errorf("unsupported dependency file type: %s", dependencyFileType)
161185
}
162186

187+
log.Info("uv command", "cmd", uvPath, "args", args)
188+
163189
virtualEnvPath := path.Join(p.State.WorkingPath, ".venv")
164190
uvCacheDir := path.Join(p.State.WorkingPath, ".uv-cache")
165191
cmd := exec.CommandContext(ctx, uvPath, args...)

internal/service/decode_service.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/langgenius/dify-plugin-daemon/pkg/entities"
1010
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
1111
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
12+
"github.com/langgenius/dify-plugin-daemon/pkg/utils/log"
1213
)
1314

1415
func FetchPluginFromIdentifier(
@@ -40,6 +41,7 @@ func DecodePluginFromIdentifier(
4041
manager := plugin_manager.Manager()
4142
pkgFile, err := manager.GetPackage(pluginUniqueIdentifier)
4243
if err != nil {
44+
log.Error("failed to get plugin package", "error", err)
4345
return exception.BadRequestError(err).ToResponse()
4446
}
4547

@@ -51,9 +53,17 @@ func DecodePluginFromIdentifier(
5153
},
5254
)
5355
if err != nil {
56+
log.Error("failed to get plugin package", "error", err)
5457
return exception.BadRequestError(err).ToResponse()
5558
}
5659

60+
defer func(zipDecoder *decoder.ZipPluginDecoder) {
61+
err := zipDecoder.Close()
62+
if err != nil {
63+
log.Error("failed to close zip decoder", "error", err)
64+
}
65+
}(zipDecoder)
66+
5767
verification, _ := zipDecoder.Verification()
5868
if verification == nil && zipDecoder.Verified() {
5969
verification = decoder.DefaultVerification()

0 commit comments

Comments
 (0)