Skip to content

Commit 26a6710

Browse files
authored
feat: regenerate requirement.txt (#720)
1 parent d7ff045 commit 26a6710

4 files changed

Lines changed: 329 additions & 41 deletions

File tree

cmd/commandline/plugin/templates/python/.difyignore

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,6 @@ ipython_config.py
9191
# install all needed dependencies.
9292
Pipfile.lock
9393

94-
# UV
95-
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
96-
# This is especially recommended for binary packages to ensure reproducibility, and is more
97-
# commonly ignored for libraries.
98-
uv.lock
99-
10094
# poetry
10195
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
10296
# This is especially recommended for binary packages to ensure reproducibility, and is more
@@ -181,4 +175,3 @@ Thumbs.db
181175
# Dify plugin packages
182176
# To prevent packaging repetitively
183177
*.difypkg
184-

pkg/plugin_packager/decoder/fs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func (d *FSPluginDecoder) Open() error {
6060
return nil
6161
}
6262

63+
func (d *FSPluginDecoder) Root() string {
64+
return d.root
65+
}
66+
6367
func (d *FSPluginDecoder) Walk(fn func(filename string, dir string) error) error {
6468
// read .difyignore file
6569
ignorePatterns := []gitignore.Pattern{}

pkg/plugin_packager/packager/packager.go

Lines changed: 175 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@ import (
55
"bytes"
66
"errors"
77
"fmt"
8+
"os"
9+
"os/exec"
810
"path/filepath"
911
"sort"
1012
"strings"
1113

14+
"github.com/langgenius/dify-plugin-daemon/pkg/entities/constants"
1215
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
1316
)
1417

18+
const generatedRequirementsFilename = "requirements.txt"
19+
1520
type Packager struct {
1621
decoder decoder.PluginDecoder
1722
manifest string // manifest file path
1823
}
1924

25+
type packageFile struct {
26+
Path string
27+
Content []byte
28+
}
29+
2030
func NewPackager(decoder decoder.PluginDecoder) *Packager {
2131
return &Packager{
2232
decoder: decoder,
@@ -30,66 +40,197 @@ func (p *Packager) Pack(maxSize int64) ([]byte, error) {
3040
return nil, err
3141
}
3242

43+
filesToPackage, err := p.collectFiles()
44+
if err != nil {
45+
return nil, err
46+
}
47+
3348
zipBuffer := new(bytes.Buffer)
3449
zipWriter := zip.NewWriter(zipBuffer)
3550

3651
totalSize := int64(0)
37-
3852
var files []FileInfoWithPath
3953

40-
err = p.decoder.Walk(func(filename, dir string) error {
41-
fullPath := filepath.Join(dir, filename)
42-
file, err := p.decoder.ReadFile(fullPath)
54+
for _, file := range filesToPackage {
55+
totalSize, files, err = writePackagedFile(zipWriter, file.Path, file.Content, totalSize, maxSize, files)
4356
if err != nil {
44-
return err
45-
}
46-
fileSize := int64(len(file))
47-
files = append(files, FileInfoWithPath{Path: fullPath, Size: fileSize})
48-
totalSize += fileSize
49-
if totalSize > maxSize {
50-
sort.Slice(files, func(i, j int) bool {
51-
return files[i].Size > files[j].Size
52-
})
53-
fileTop5Info := ""
54-
top := 5
55-
if len(files) < 5 {
56-
top = len(files)
57-
}
58-
for i := 0; i < top; i++ {
59-
fileTop5Info += fmt.Sprintf("%d. name: %s, size: %d bytes\n", i+1, files[i].Path, files[i].Size)
60-
}
61-
errMsg := fmt.Sprintf("Plugin package size is too large. Please ensure the uncompressed size is less than %d bytes.\nPackaged file info:\n%s",
62-
maxSize, fileTop5Info)
63-
return errors.New(errMsg)
57+
return nil, err
6458
}
59+
}
6560

66-
// ISSUES: Windows path separator is \, but zip requires /, to avoid this we just simply replace all \ with / for now
67-
// TODO: find a better solution
68-
fullPath = strings.ReplaceAll(fullPath, "\\", "/")
61+
err = zipWriter.Close()
62+
if err != nil {
63+
return nil, err
64+
}
6965

70-
zipFile, err := zipWriter.Create(fullPath)
71-
if err != nil {
72-
return err
73-
}
66+
return zipBuffer.Bytes(), nil
67+
}
68+
69+
func (p *Packager) collectFiles() ([]packageFile, error) {
70+
files := make([]packageFile, 0)
71+
existingPaths := make(map[string]struct{})
7472

75-
_, err = zipFile.Write(file)
73+
err := p.decoder.Walk(func(filename, dir string) error {
74+
fullPath := filepath.Join(dir, filename)
75+
content, err := p.decoder.ReadFile(fullPath)
7676
if err != nil {
7777
return err
7878
}
7979

80+
files = append(files, packageFile{
81+
Path: fullPath,
82+
Content: content,
83+
})
84+
existingPaths[fullPath] = struct{}{}
8085
return nil
8186
})
87+
if err != nil {
88+
return nil, err
89+
}
8290

91+
generatedFiles, err := p.generatedFiles()
8392
if err != nil {
8493
return nil, err
8594
}
8695

87-
err = zipWriter.Close()
96+
for _, file := range generatedFiles {
97+
if _, exists := existingPaths[file.Path]; exists {
98+
continue
99+
}
100+
files = append(files, file)
101+
}
102+
103+
sort.Slice(files, func(i, j int) bool {
104+
return files[i].Path < files[j].Path
105+
})
106+
107+
return files, nil
108+
}
109+
110+
func (p *Packager) generatedFiles() ([]packageFile, error) {
111+
manifest, err := p.fetchManifest()
88112
if err != nil {
89113
return nil, err
90114
}
91115

92-
return zipBuffer.Bytes(), nil
116+
if manifest.Meta.Runner.Language != constants.Python {
117+
return nil, nil
118+
}
119+
120+
if _, err := p.decoder.Stat(generatedRequirementsFilename); err == nil {
121+
return []packageFile{}, nil
122+
} else if !os.IsNotExist(err) {
123+
return nil, err
124+
}
125+
126+
if _, err := p.decoder.Stat("pyproject.toml"); err != nil {
127+
if os.IsNotExist(err) {
128+
return []packageFile{}, nil
129+
}
130+
return nil, err
131+
}
132+
133+
if _, err := p.decoder.Stat("uv.lock"); err != nil {
134+
if os.IsNotExist(err) {
135+
return []packageFile{}, nil
136+
}
137+
return nil, err
138+
}
139+
140+
fsDecoder, ok := p.decoder.(*decoder.FSPluginDecoder)
141+
if !ok {
142+
return []packageFile{}, nil
143+
}
144+
145+
requirements, err := exportRequirementsFromUv(fsDecoder.Root())
146+
if err != nil {
147+
return nil, err
148+
}
149+
150+
return []packageFile{
151+
{
152+
Path: generatedRequirementsFilename,
153+
Content: requirements,
154+
},
155+
}, nil
156+
}
157+
158+
func exportRequirementsFromUv(root string) ([]byte, error) {
159+
uvPath := os.Getenv("UV_PATH")
160+
if uvPath == "" {
161+
var err error
162+
uvPath, err = exec.LookPath("uv")
163+
if err != nil {
164+
return nil, fmt.Errorf("failed to find uv executable for exporting requirements.txt: %w", err)
165+
}
166+
}
167+
168+
args := []string{
169+
"export",
170+
"--frozen",
171+
"--format", "requirements.txt",
172+
"--no-group", "dev",
173+
"--no-emit-project",
174+
"--no-hashes",
175+
"--no-header",
176+
"--no-annotate",
177+
}
178+
179+
cmd := exec.Command(uvPath, args...)
180+
cmd.Dir = root
181+
182+
output, err := cmd.Output()
183+
if err != nil {
184+
if exitErr := new(exec.ExitError); errors.As(err, &exitErr) {
185+
return nil, fmt.Errorf("uv export failed: %s", strings.TrimSpace(string(exitErr.Stderr)))
186+
}
187+
return nil, fmt.Errorf("uv export failed: %w", err)
188+
}
189+
190+
return output, nil
191+
}
192+
193+
func writePackagedFile(
194+
zipWriter *zip.Writer,
195+
fullPath string,
196+
file []byte,
197+
totalSize int64,
198+
maxSize int64,
199+
files []FileInfoWithPath,
200+
) (int64, []FileInfoWithPath, error) {
201+
fileSize := int64(len(file))
202+
files = append(files, FileInfoWithPath{Path: fullPath, Size: fileSize})
203+
totalSize += fileSize
204+
if totalSize > maxSize {
205+
sort.Slice(files, func(i, j int) bool {
206+
return files[i].Size > files[j].Size
207+
})
208+
fileTop5Info := ""
209+
top := 5
210+
if len(files) < 5 {
211+
top = len(files)
212+
}
213+
for i := 0; i < top; i++ {
214+
fileTop5Info += fmt.Sprintf("%d. name: %s, size: %d bytes\n", i+1, files[i].Path, files[i].Size)
215+
}
216+
errMsg := fmt.Sprintf("Plugin package size is too large. Please ensure the uncompressed size is less than %d bytes.\nPackaged file info:\n%s",
217+
maxSize, fileTop5Info)
218+
return totalSize, files, errors.New(errMsg)
219+
}
220+
221+
fullPath = strings.ReplaceAll(fullPath, "\\", "/")
222+
223+
zipFile, err := zipWriter.Create(fullPath)
224+
if err != nil {
225+
return totalSize, files, err
226+
}
227+
228+
_, err = zipFile.Write(file)
229+
if err != nil {
230+
return totalSize, files, err
231+
}
232+
233+
return totalSize, files, nil
93234
}
94235

95236
type FileInfoWithPath struct {

0 commit comments

Comments
 (0)