feat: add import info from alr-repo.toml #134
| @@ -11,7 +11,7 @@ | ||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||
|         <text x="33.5" y="14">coverage</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.4%</text> | ||||
|         <text x="86" y="14">19.4%</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">18.8%</text> | ||||
|         <text x="86" y="14">18.8%</text> | ||||
|     </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
| @@ -0,0 +1,5 @@ | ||||
| - name: alr-repo | ||||
|   url: https://gitea.plemya-x.ru/Plemya-x/repo-for-tests | ||||
|   ref: main | ||||
|   mirrors: | ||||
|   - https://github.com/example/example.git | ||||
							
								
								
									
										40
									
								
								e2e-tests/issue_129_repo_toml_import_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_129_repo_toml_import_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.alt-gnome.ru/capytest" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue129RepoTomlImportTest(t *testing.T) { | ||||
| 	runMatrixSuite( | ||||
| 		t, | ||||
| 		"issue-129-repo-toml-import-test", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r capytest.Runner) { | ||||
| 			defaultPrepare(t, r) | ||||
|  | ||||
| 			r.Command("alr", "config", "get", "repos"). | ||||
| 				ExpectStdoutMatchesSnapshot(). | ||||
| 				Run(t) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										416
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,416 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"go/ast" | ||||
| 	"go/format" | ||||
| 	"go/parser" | ||||
| 	"go/token" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| 	"unicode" | ||||
|  | ||||
| 	"golang.org/x/text/cases" | ||||
| 	"golang.org/x/text/language" | ||||
| ) | ||||
|  | ||||
| type MethodInfo struct { | ||||
| 	Name       string | ||||
| 	Params     []ParamInfo | ||||
| 	Results    []ResultInfo | ||||
| 	EntityName string | ||||
| } | ||||
|  | ||||
| type ParamInfo struct { | ||||
| 	Name string | ||||
| 	Type string | ||||
| } | ||||
|  | ||||
| type ResultInfo struct { | ||||
| 	Name  string | ||||
| 	Type  string | ||||
| 	Index int | ||||
| } | ||||
|  | ||||
| func extractImports(node *ast.File) []string { | ||||
| 	var imports []string | ||||
| 	for _, imp := range node.Imports { | ||||
| 		if imp.Path.Value != "" { | ||||
| 			imports = append(imports, imp.Path.Value) | ||||
| 		} | ||||
| 	} | ||||
| 	return imports | ||||
| } | ||||
|  | ||||
| func output(path string, buf bytes.Buffer) { | ||||
| 	formatted, err := format.Source(buf.Bytes()) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("formatting: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	outPath := strings.TrimSuffix(path, ".go") + "_gen.go" | ||||
| 	outFile, err := os.Create(outPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("create file: %v", err) | ||||
| 	} | ||||
| 	_, err = outFile.Write(formatted) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("writing output: %v", err) | ||||
| 	} | ||||
| 	outFile.Close() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	path := os.Getenv("GOFILE") | ||||
| 	if path == "" { | ||||
| 		log.Fatal("GOFILE must be set") | ||||
| 	} | ||||
|  | ||||
| 	if len(os.Args) < 2 { | ||||
| 		log.Fatal("At least one entity name must be provided") | ||||
| 	} | ||||
|  | ||||
| 	entityNames := os.Args[1:] | ||||
|  | ||||
| 	fset := token.NewFileSet() | ||||
| 	node, err := parser.ParseFile(fset, path, nil, parser.AllErrors) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("parsing file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	packageName := node.Name.Name | ||||
|  | ||||
| 	// Find all specified entities | ||||
| 	entityData := make(map[string][]*ast.Field) | ||||
|  | ||||
| 	for _, decl := range node.Decls { | ||||
| 		genDecl, ok := decl.(*ast.GenDecl) | ||||
| 		if !ok || genDecl.Tok != token.TYPE { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, spec := range genDecl.Specs { | ||||
| 			typeSpec := spec.(*ast.TypeSpec) | ||||
| 			for _, entityName := range entityNames { | ||||
| 				if typeSpec.Name.Name == entityName { | ||||
| 					interfaceType, ok := typeSpec.Type.(*ast.InterfaceType) | ||||
| 					if !ok { | ||||
| 						log.Fatalf("entity %s is not an interface", entityName) | ||||
| 					} | ||||
| 					entityData[entityName] = interfaceType.Methods.List | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Verify all entities were found | ||||
| 	for _, entityName := range entityNames { | ||||
| 		if _, found := entityData[entityName]; !found { | ||||
| 			log.Fatalf("interface %s not found", entityName) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var buf bytes.Buffer | ||||
|  | ||||
| 	buf.WriteString(` | ||||
| // DO NOT EDIT MANUALLY. This file is generated. | ||||
|  | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
|  | ||||
| `) | ||||
|  | ||||
| 	buf.WriteString(fmt.Sprintf("package %s\n", packageName)) | ||||
|  | ||||
| 	// Generate base structures for all entities | ||||
| 	baseStructs(&buf, entityNames, extractImports(node)) | ||||
|  | ||||
| 	// Generate method-specific code for each entity | ||||
| 	for _, entityName := range entityNames { | ||||
| 		methods := parseMethodsFromFields(entityName, entityData[entityName]) | ||||
| 		argsGen(&buf, methods) | ||||
| 	} | ||||
|  | ||||
| 	output(path, buf) | ||||
| } | ||||
|  | ||||
| func parseMethodsFromFields(entityName string, fields []*ast.Field) []MethodInfo { | ||||
| 	var methods []MethodInfo | ||||
|  | ||||
| 	for _, field := range fields { | ||||
| 		if len(field.Names) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		methodName := field.Names[0].Name | ||||
| 		funcType, ok := field.Type.(*ast.FuncType) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		method := MethodInfo{ | ||||
| 			Name:       methodName, | ||||
| 			EntityName: entityName, | ||||
| 		} | ||||
|  | ||||
| 		// Parse parameters, excluding context.Context | ||||
| 		if funcType.Params != nil { | ||||
| 			for i, param := range funcType.Params.List { | ||||
| 				paramType := typeToString(param.Type) | ||||
| 				// Skip context.Context parameters | ||||
| 				if paramType == "context.Context" { | ||||
| 					continue | ||||
| 				} | ||||
| 				if len(param.Names) == 0 { | ||||
| 					method.Params = append(method.Params, ParamInfo{ | ||||
| 						Name: fmt.Sprintf("Arg%d", i), | ||||
| 						Type: paramType, | ||||
| 					}) | ||||
| 				} else { | ||||
| 					for _, name := range param.Names { | ||||
| 						method.Params = append(method.Params, ParamInfo{ | ||||
| 							Name: cases.Title(language.Und, cases.NoLower).String(name.Name), | ||||
| 							Type: paramType, | ||||
| 						}) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Parse results | ||||
| 		if funcType.Results != nil { | ||||
| 			resultIndex := 0 | ||||
| 			for _, result := range funcType.Results.List { | ||||
| 				resultType := typeToString(result.Type) | ||||
| 				if resultType == "error" { | ||||
| 					continue // Skip error in response struct | ||||
| 				} | ||||
|  | ||||
| 				if len(result.Names) == 0 { | ||||
| 					method.Results = append(method.Results, ResultInfo{ | ||||
| 						Name:  fmt.Sprintf("Result%d", resultIndex), | ||||
| 						Type:  resultType, | ||||
| 						Index: resultIndex, | ||||
| 					}) | ||||
| 				} else { | ||||
| 					for _, name := range result.Names { | ||||
| 						method.Results = append(method.Results, ResultInfo{ | ||||
| 							Name:  cases.Title(language.Und, cases.NoLower).String(name.Name), | ||||
| 							Type:  resultType, | ||||
| 							Index: resultIndex, | ||||
| 						}) | ||||
| 					} | ||||
| 				} | ||||
| 				resultIndex++ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		methods = append(methods, method) | ||||
| 	} | ||||
|  | ||||
| 	return methods | ||||
| } | ||||
|  | ||||
| func argsGen(buf *bytes.Buffer, methods []MethodInfo) { | ||||
| 	// Add template functions first | ||||
| 	funcMap := template.FuncMap{ | ||||
| 		"lowerFirst": func(s string) string { | ||||
| 			if len(s) == 0 { | ||||
| 				return s | ||||
| 			} | ||||
| 			return strings.ToLower(s[:1]) + s[1:] | ||||
| 		}, | ||||
| 		"zeroValue": func(typeName string) string { | ||||
| 			typeName = strings.TrimSpace(typeName) | ||||
|  | ||||
| 			switch typeName { | ||||
| 			case "string": | ||||
| 				return "\"\"" | ||||
| 			case "int", "int8", "int16", "int32", "int64": | ||||
| 				return "0" | ||||
| 			case "uint", "uint8", "uint16", "uint32", "uint64": | ||||
| 				return "0" | ||||
| 			case "float32", "float64": | ||||
| 				return "0.0" | ||||
| 			case "bool": | ||||
| 				return "false" | ||||
| 			} | ||||
|  | ||||
| 			if strings.HasPrefix(typeName, "*") { | ||||
| 				return "nil" | ||||
| 			} | ||||
| 			if strings.HasPrefix(typeName, "[]") || | ||||
| 				strings.HasPrefix(typeName, "map[") || | ||||
| 				strings.HasPrefix(typeName, "chan ") { | ||||
| 				return "nil" | ||||
| 			} | ||||
|  | ||||
| 			if typeName == "interface{}" { | ||||
| 				return "nil" | ||||
| 			} | ||||
|  | ||||
| 			// If external type: pkg.Type | ||||
| 			if strings.Contains(typeName, ".") { | ||||
| 				return typeName + "{}" | ||||
| 			} | ||||
|  | ||||
| 			// If starts with uppercase — likely struct | ||||
| 			if len(typeName) > 0 && unicode.IsUpper(rune(typeName[0])) { | ||||
| 				return typeName + "{}" | ||||
| 			} | ||||
|  | ||||
| 			return "nil" | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	argsTemplate := template.Must(template.New("args").Funcs(funcMap).Parse(` | ||||
| {{range .}} | ||||
| type {{.EntityName}}{{.Name}}Args struct { | ||||
| {{range .Params}}	{{.Name}} {{.Type}} | ||||
| {{end}}} | ||||
|  | ||||
| type {{.EntityName}}{{.Name}}Resp struct { | ||||
| {{range .Results}}	{{.Name}} {{.Type}} | ||||
| {{end}}} | ||||
|  | ||||
| func (s *{{.EntityName}}RPC) {{.Name}}(ctx context.Context, {{range $i, $p := .Params}}{{if $i}}, {{end}}{{lowerFirst $p.Name}} {{$p.Type}}{{end}}) ({{range $i, $r := .Results}}{{if $i}}, {{end}}{{$r.Type}}{{end}}{{if .Results}}, {{end}}error) { | ||||
| 	var resp *{{.EntityName}}{{.Name}}Resp | ||||
| 	err := s.client.Call("Plugin.{{.Name}}", &{{.EntityName}}{{.Name}}Args{ | ||||
| {{range .Params}}		{{.Name}}: {{lowerFirst .Name}}, | ||||
| {{end}}	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return {{range $i, $r := .Results}}{{if $i}}, {{end}}{{zeroValue $r.Type}}{{end}}{{if .Results}}, {{end}}err | ||||
| 	} | ||||
| 	return {{range $i, $r := .Results}}{{if $i}}, {{end}}resp.{{$r.Name}}{{end}}{{if .Results}}, {{end}}nil | ||||
| } | ||||
|  | ||||
| func (s *{{.EntityName}}RPCServer) {{.Name}}(args *{{.EntityName}}{{.Name}}Args, resp *{{.EntityName}}{{.Name}}Resp) error { | ||||
| 	{{if .Results}}{{range $i, $r := .Results}}{{if $i}}, {{end}}{{lowerFirst $r.Name}}{{end}}, err := {{else}}err := {{end}}s.Impl.{{.Name}}(context.Background(),{{range $i, $p := .Params}}{{if $i}}, {{end}}args.{{$p.Name}}{{end}}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	{{if .Results}}*resp = {{.EntityName}}{{.Name}}Resp{ | ||||
| {{range .Results}}		{{.Name}}: {{lowerFirst .Name}}, | ||||
| {{end}}	} | ||||
| 	{{else}}*resp = {{.EntityName}}{{.Name}}Resp{} | ||||
| 	{{end}}return nil | ||||
| } | ||||
| {{end}} | ||||
| `)) | ||||
|  | ||||
| 	err := argsTemplate.Execute(buf, methods) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("execute args template: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func typeToString(expr ast.Expr) string { | ||||
| 	switch t := expr.(type) { | ||||
| 	case *ast.Ident: | ||||
| 		return t.Name | ||||
| 	case *ast.StarExpr: | ||||
| 		return "*" + typeToString(t.X) | ||||
| 	case *ast.ArrayType: | ||||
| 		return "[]" + typeToString(t.Elt) | ||||
| 	case *ast.SelectorExpr: | ||||
| 		xStr := typeToString(t.X) | ||||
| 		if xStr == "context" && t.Sel.Name == "Context" { | ||||
| 			return "context.Context" | ||||
| 		} | ||||
| 		return xStr + "." + t.Sel.Name | ||||
| 	case *ast.InterfaceType: | ||||
| 		return "interface{}" | ||||
| 	default: | ||||
| 		return "interface{}" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func baseStructs(buf *bytes.Buffer, entityNames, imports []string) { | ||||
| 	// Ensure "context" is included in imports | ||||
| 	updatedImports := imports | ||||
| 	hasContext := false | ||||
| 	for _, imp := range imports { | ||||
| 		if strings.Contains(imp, `"context"`) { | ||||
| 			hasContext = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !hasContext { | ||||
| 		updatedImports = append(updatedImports, `"context"`) | ||||
| 	} | ||||
|  | ||||
| 	contentTemplate := template.Must(template.New("").Parse(` | ||||
| import ( | ||||
| 	"net/rpc" | ||||
|  | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
| {{range .Imports}}	{{.}} | ||||
| {{end}} | ||||
| ) | ||||
|  | ||||
| {{range .EntityNames}} | ||||
| type {{ . }}Plugin struct { | ||||
| 	Impl {{ . }} | ||||
| } | ||||
|  | ||||
| type {{ . }}RPCServer struct { | ||||
| 	Impl {{ . }} | ||||
| } | ||||
|  | ||||
| type {{ . }}RPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| func (p *{{ . }}Plugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &{{ . }}RPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *{{ . }}Plugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &{{ . }}RPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| {{end}} | ||||
| `)) | ||||
| 	err := contentTemplate.Execute(buf, struct { | ||||
| 		EntityNames []string | ||||
| 		Imports     []string | ||||
| 	}{ | ||||
| 		EntityNames: entityNames, | ||||
| 		Imports:     updatedImports, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("execute template: %v", err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										15
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								go.mod
									
									
									
									
									
								
							| @@ -34,8 +34,8 @@ require ( | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/urfave/cli/v2 v2.25.7 | ||||
| 	github.com/vmihailenco/msgpack/v5 v5.3.5 | ||||
| 	go.alt-gnome.ru/capytest v0.0.2 | ||||
| 	go.alt-gnome.ru/capytest/providers/podman v0.0.2 | ||||
| 	go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 | ||||
| 	go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 | ||||
| 	go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 | ||||
| 	golang.org/x/crypto v0.36.0 | ||||
| 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | ||||
| @@ -77,6 +77,9 @@ require ( | ||||
| 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||||
| 	github.com/fatih/color v1.7.0 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.9.0 // indirect | ||||
| 	github.com/gkampitakis/ciinfo v0.3.2 // indirect | ||||
| 	github.com/gkampitakis/go-diff v1.3.2 // indirect | ||||
| 	github.com/gkampitakis/go-snaps v0.5.13 // indirect | ||||
| 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | ||||
| 	github.com/go-logfmt/logfmt v0.6.0 // indirect | ||||
| 	github.com/go-viper/mapstructure/v2 v2.3.0 // indirect | ||||
| @@ -101,7 +104,10 @@ require ( | ||||
| 	github.com/klauspost/compress v1.17.11 // indirect | ||||
| 	github.com/klauspost/pgzip v1.2.6 // indirect | ||||
| 	github.com/knadh/koanf/maps v0.1.2 // indirect | ||||
| 	github.com/kr/pretty v0.3.1 // indirect | ||||
| 	github.com/kr/text v0.2.0 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||
| 	github.com/maruel/natural v1.1.1 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||
| 	github.com/mattn/go-localereader v0.0.1 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.16 // indirect | ||||
| @@ -120,6 +126,7 @@ require ( | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | ||||
| 	github.com/rogpeppe/go-internal v1.13.1 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||||
| 	github.com/shopspring/decimal v1.3.1 // indirect | ||||
| @@ -127,6 +134,10 @@ require ( | ||||
| 	github.com/spf13/cast v1.7.1 // indirect | ||||
| 	github.com/syndtr/goleveldb v1.0.0 // indirect | ||||
| 	github.com/therootcompany/xz v1.0.1 // indirect | ||||
| 	github.com/tidwall/gjson v1.18.0 // indirect | ||||
| 	github.com/tidwall/match v1.1.1 // indirect | ||||
| 	github.com/tidwall/pretty v1.2.1 // indirect | ||||
| 	github.com/tidwall/sjson v1.2.5 // indirect | ||||
| 	github.com/ulikunitz/xz v0.5.12 // indirect | ||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.3.3 // indirect | ||||
|   | ||||
							
								
								
									
										27
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								go.sum
									
									
									
									
									
								
							| @@ -104,6 +104,7 @@ github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEyc | ||||
| github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= | ||||
| github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= | ||||
| @@ -134,6 +135,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= | ||||
| github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= | ||||
| github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= | ||||
| github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= | ||||
| github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= | ||||
| github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= | ||||
| github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q= | ||||
| github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= | ||||
| github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= | ||||
| github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | ||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||
| @@ -286,6 +293,8 @@ github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQ | ||||
| github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= | ||||
| github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= | ||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||
| github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= | ||||
| github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||
| @@ -345,6 +354,7 @@ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0 | ||||
| github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||
| github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | ||||
| github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= | ||||
| github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| @@ -358,6 +368,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ | ||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||
| github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||
| github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | ||||
| github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= | ||||
| github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||
| @@ -393,6 +404,16 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd | ||||
| github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | ||||
| github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||
| github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | ||||
| github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= | ||||
| github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= | ||||
| github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= | ||||
| github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= | ||||
| github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= | ||||
| github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= | ||||
| github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= | ||||
| github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= | ||||
| github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= | ||||
| github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= | ||||
| github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= | ||||
| github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= | ||||
| github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | ||||
| @@ -413,8 +434,10 @@ gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/Yjui | ||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | ||||
| go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8= | ||||
| go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg= | ||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.2 h1:fTQ9fmYiONgL8dJvyMB+irCfuojIVaomnqto6bl6HjU= | ||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.2/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg= | ||||
| go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g= | ||||
| go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y= | ||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE= | ||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg= | ||||
| go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY= | ||||
| go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk= | ||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
|   | ||||
							
								
								
									
										39
									
								
								internal.go
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								internal.go
									
									
									
									
									
								
							| @@ -84,6 +84,43 @@ func InternalBuildCmd() *cli.Command { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func InternalReposCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:     "_internal-repos", | ||||
| 		HideHelp: true, | ||||
| 		Hidden:   true, | ||||
| 		Action: utils.RootNeededAction(func(ctx *cli.Context) error { | ||||
| 			logger.SetupForGoPlugin() | ||||
|  | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			pluginCfg := build.GetPluginServeCommonConfig() | ||||
| 			pluginCfg.Plugins = map[string]plugin.Plugin{ | ||||
| 				"repos": &build.ReposExecutorPlugin{ | ||||
| 					Impl: build.NewRepos( | ||||
| 						deps.Repos, | ||||
| 					), | ||||
| 				}, | ||||
| 			} | ||||
| 			plugin.Serve(pluginCfg) | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func InternalInstallCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:     "_internal-installer", | ||||
| @@ -125,7 +162,7 @@ func InternalInstallCmd() *cli.Command { | ||||
| 			plugin.Serve(&plugin.ServeConfig{ | ||||
| 				HandshakeConfig: build.HandshakeConfig, | ||||
| 				Plugins: map[string]plugin.Plugin{ | ||||
| 					"installer": &build.InstallerPlugin{ | ||||
| 					"installer": &build.InstallerExecutorPlugin{ | ||||
| 						Impl: build.NewInstaller( | ||||
| 							manager.Detect(), | ||||
| 						), | ||||
|   | ||||
| @@ -176,25 +176,6 @@ type ScriptResolverExecutor interface { | ||||
| 	ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo | ||||
| } | ||||
|  | ||||
| type ScriptExecutor interface { | ||||
| 	ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) | ||||
| 	ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) | ||||
| 	PrepareDirs( | ||||
| 		ctx context.Context, | ||||
| 		input *BuildInput, | ||||
| 		basePkg string, | ||||
| 	) error | ||||
| 	ExecuteSecondPass( | ||||
| 		ctx context.Context, | ||||
| 		input *BuildInput, | ||||
| 		sf *alrsh.ScriptFile, | ||||
| 		varsOfPackages []*alrsh.Package, | ||||
| 		repoDeps []string, | ||||
| 		builtDeps []*BuiltDep, | ||||
| 		basePkg string, | ||||
| 	) ([]*BuiltDep, error) | ||||
| } | ||||
|  | ||||
| type CacheExecutor interface { | ||||
| 	CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error) | ||||
| } | ||||
| @@ -211,14 +192,6 @@ type CheckerExecutor interface { | ||||
| 	) (bool, error) | ||||
| } | ||||
|  | ||||
| type InstallerExecutor interface { | ||||
| 	InstallLocal(paths []string, opts *manager.Opts) error | ||||
| 	Install(pkgs []string, opts *manager.Opts) error | ||||
| 	Remove(pkgs []string, opts *manager.Opts) error | ||||
|  | ||||
| 	RemoveAlreadyInstalled(pkgs []string) ([]string, error) | ||||
| } | ||||
|  | ||||
| type SourcesInput struct { | ||||
| 	Sources   []string | ||||
| 	Checksums []string | ||||
| @@ -499,6 +472,7 @@ func (b *Builder) removeBuildDeps(ctx context.Context, input interface { | ||||
|  | ||||
| 		if remove { | ||||
| 			err = b.installerExecutor.Remove( | ||||
| 				ctx, | ||||
| 				deps, | ||||
| 				&manager.Opts{ | ||||
| 					NoConfirm: !input.BuildOpts().Interactive, | ||||
| @@ -545,6 +519,7 @@ func (b *Builder) InstallALRPackages( | ||||
| 		} | ||||
|  | ||||
| 		err = b.installerExecutor.InstallLocal( | ||||
| 			ctx, | ||||
| 			GetBuiltPaths(res), | ||||
| 			&manager.Opts{ | ||||
| 				NoConfirm: !input.BuildOpts().Interactive, | ||||
| @@ -645,7 +620,7 @@ func (i *Builder) installBuildDeps( | ||||
| 	var deps []string | ||||
| 	var err error | ||||
| 	if len(pkgs) > 0 { | ||||
| 		deps, err = i.installerExecutor.RemoveAlreadyInstalled(pkgs) | ||||
| 		deps, err = i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| @@ -668,7 +643,7 @@ func (i *Builder) installOptDeps( | ||||
| 	pkgs []string, | ||||
| ) ([]*BuiltDep, error) { | ||||
| 	var builtDeps []*BuiltDep | ||||
| 	optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) | ||||
| 	optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -710,7 +685,7 @@ func (i *Builder) InstallPkgs( | ||||
| 	} | ||||
|  | ||||
| 	if len(builtDeps) > 0 { | ||||
| 		err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{ | ||||
| 		err = i.installerExecutor.InstallLocal(ctx, GetBuiltPaths(builtDeps), &manager.Opts{ | ||||
| 			NoConfirm: !input.BuildOpts().Interactive, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| @@ -719,7 +694,7 @@ func (i *Builder) InstallPkgs( | ||||
| 	} | ||||
|  | ||||
| 	if len(repoDeps) > 0 { | ||||
| 		err = i.installerExecutor.Install(repoDeps, &manager.Opts{ | ||||
| 		err = i.installerExecutor.Install(ctx, repoDeps, &manager.Opts{ | ||||
| 			NoConfirm: !input.BuildOpts().Interactive, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
|   | ||||
| @@ -17,6 +17,8 @@ | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||
| ) | ||||
|  | ||||
| @@ -28,19 +30,19 @@ func NewInstaller(mgr manager.Manager) *Installer { | ||||
|  | ||||
| type Installer struct{ mgr manager.Manager } | ||||
|  | ||||
| func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error { | ||||
| func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error { | ||||
| 	return i.mgr.InstallLocal(opts, paths...) | ||||
| } | ||||
|  | ||||
| func (i *Installer) Install(pkgs []string, opts *manager.Opts) error { | ||||
| func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||
| 	return i.mgr.Install(opts, pkgs...) | ||||
| } | ||||
|  | ||||
| func (i *Installer) Remove(pkgs []string, opts *manager.Opts) error { | ||||
| func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||
| 	return i.mgr.Remove(opts, pkgs...) | ||||
| } | ||||
|  | ||||
| func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) { | ||||
| func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) { | ||||
| 	filteredPackages := []string{} | ||||
|  | ||||
| 	for _, dep := range pkgs { | ||||
|   | ||||
							
								
								
									
										144
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| ) | ||||
|  | ||||
| var pluginMap = map[string]plugin.Plugin{ | ||||
| 	"script-executor": &ScriptExecutorPlugin{}, | ||||
| 	"installer":       &InstallerExecutorPlugin{}, | ||||
| 	"repos":           &ReposExecutorPlugin{}, | ||||
| } | ||||
|  | ||||
| var HandshakeConfig = plugin.HandshakeConfig{ | ||||
| 	ProtocolVersion:  1, | ||||
| 	MagicCookieKey:   "ALR_PLUGIN", | ||||
| 	MagicCookieValue: "-", | ||||
| } | ||||
|  | ||||
| func setCommonCmdEnv(cmd *exec.Cmd) { | ||||
| 	cmd.Env = []string{ | ||||
| 		"HOME=/var/cache/alr", | ||||
| 		"LOGNAME=alr", | ||||
| 		"USER=alr", | ||||
| 		"PATH=/usr/bin:/bin:/usr/local/bin", | ||||
| 	} | ||||
| 	for _, env := range os.Environ() { | ||||
| 		if strings.HasPrefix(env, "LANG=") || | ||||
| 			strings.HasPrefix(env, "LANGUAGE=") || | ||||
| 			strings.HasPrefix(env, "LC_") || | ||||
| 			strings.HasPrefix(env, "ALR_LOG_LEVEL=") { | ||||
| 			cmd.Env = append(cmd.Env, env) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetPluginServeCommonConfig() *plugin.ServeConfig { | ||||
| 	return &plugin.ServeConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Logger: hclog.New(&hclog.LoggerOptions{ | ||||
| 			Name:        "plugin", | ||||
| 			Output:      os.Stderr, | ||||
| 			Level:       hclog.Trace, | ||||
| 			JSONFormat:  true, | ||||
| 			DisableTime: true, | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetSafeInstaller() (InstallerExecutor, func(), error) { | ||||
| 	return getSafeExecutor[InstallerExecutor]("_internal-installer", "installer") | ||||
| } | ||||
|  | ||||
| func GetSafeScriptExecutor() (ScriptExecutor, func(), error) { | ||||
| 	return getSafeExecutor[ScriptExecutor]("_internal-safe-script-executor", "script-executor") | ||||
| } | ||||
|  | ||||
| func GetSafeReposExecutor() (ReposExecutor, func(), error) { | ||||
| 	return getSafeExecutor[ReposExecutor]("_internal-repos", "repos") | ||||
| } | ||||
|  | ||||
| func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) { | ||||
| 	var err error | ||||
|  | ||||
| 	executable, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		var zero T | ||||
| 		return zero, nil, err | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command(executable, subCommand) | ||||
| 	setCommonCmdEnv(cmd) | ||||
|  | ||||
| 	client := plugin.NewClient(&plugin.ClientConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Plugins:         pluginMap, | ||||
| 		Cmd:             cmd, | ||||
| 		Logger:          logger.GetHCLoggerAdapter(), | ||||
| 		SkipHostEnv:     true, | ||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | ||||
| 			Group: "alr", | ||||
| 		}, | ||||
| 		SyncStderr: os.Stderr, | ||||
| 	}) | ||||
| 	rpcClient, err := client.Client() | ||||
| 	if err != nil { | ||||
| 		var zero T | ||||
| 		return zero, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var cleanupOnce sync.Once | ||||
| 	cleanup := func() { | ||||
| 		cleanupOnce.Do(func() { | ||||
| 			client.Kill() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			slog.Debug("close executor") | ||||
| 			cleanup() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	raw, err := rpcClient.Dispense(pluginName) | ||||
| 	if err != nil { | ||||
| 		var zero T | ||||
| 		return zero, nil, err | ||||
| 	} | ||||
|  | ||||
| 	executor, ok := raw.(T) | ||||
| 	if !ok { | ||||
| 		var zero T | ||||
| 		err = fmt.Errorf("dispensed object is not a %T (got %T)", zero, raw) | ||||
| 		return zero, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return executor, cleanup, nil | ||||
| } | ||||
							
								
								
									
										60
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| ) | ||||
|  | ||||
| //go:generate go run ../../generators/plugin-generator InstallerExecutor ScriptExecutor ReposExecutor | ||||
|  | ||||
| // The Executors interfaces must use context.Context as the first parameter, | ||||
| // because the plugin-generator cannot generate code without it. | ||||
|  | ||||
| type InstallerExecutor interface { | ||||
| 	InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error | ||||
| 	Install(ctx context.Context, pkgs []string, opts *manager.Opts) error | ||||
| 	Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error | ||||
| 	RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) | ||||
| } | ||||
|  | ||||
| type ScriptExecutor interface { | ||||
| 	ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) | ||||
| 	ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) | ||||
| 	PrepareDirs( | ||||
| 		ctx context.Context, | ||||
| 		input *BuildInput, | ||||
| 		basePkg string, | ||||
| 	) error | ||||
| 	ExecuteSecondPass( | ||||
| 		ctx context.Context, | ||||
| 		input *BuildInput, | ||||
| 		sf *alrsh.ScriptFile, | ||||
| 		varsOfPackages []*alrsh.Package, | ||||
| 		repoDeps []string, | ||||
| 		builtDeps []*BuiltDep, | ||||
| 		basePkg string, | ||||
| 	) ([]*BuiltDep, error) | ||||
| } | ||||
|  | ||||
| type ReposExecutor interface { | ||||
| 	PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) | ||||
| } | ||||
							
								
								
									
										369
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,369 @@ | ||||
| // DO NOT EDIT MANUALLY. This file is generated. | ||||
|  | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"net/rpc" | ||||
|  | ||||
| 	"context" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
| ) | ||||
|  | ||||
| type InstallerExecutorPlugin struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRPCServer struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| func (p *InstallerExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &InstallerExecutorRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *InstallerExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &InstallerExecutorRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorPlugin struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPCServer struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| type ReposExecutorPlugin struct { | ||||
| 	Impl ReposExecutor | ||||
| } | ||||
|  | ||||
| type ReposExecutorRPCServer struct { | ||||
| 	Impl ReposExecutor | ||||
| } | ||||
|  | ||||
| type ReposExecutorRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| func (p *ReposExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &ReposExecutorRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *ReposExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &ReposExecutorRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| type InstallerExecutorInstallLocalArgs struct { | ||||
| 	Paths []string | ||||
| 	Opts  *manager.Opts | ||||
| } | ||||
|  | ||||
| type InstallerExecutorInstallLocalResp struct { | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPC) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error { | ||||
| 	var resp *InstallerExecutorInstallLocalResp | ||||
| 	err := s.client.Call("Plugin.InstallLocal", &InstallerExecutorInstallLocalArgs{ | ||||
| 		Paths: paths, | ||||
| 		Opts:  opts, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPCServer) InstallLocal(args *InstallerExecutorInstallLocalArgs, resp *InstallerExecutorInstallLocalResp) error { | ||||
| 	err := s.Impl.InstallLocal(context.Background(), args.Paths, args.Opts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = InstallerExecutorInstallLocalResp{} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type InstallerExecutorInstallArgs struct { | ||||
| 	Pkgs []string | ||||
| 	Opts *manager.Opts | ||||
| } | ||||
|  | ||||
| type InstallerExecutorInstallResp struct { | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPC) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||
| 	var resp *InstallerExecutorInstallResp | ||||
| 	err := s.client.Call("Plugin.Install", &InstallerExecutorInstallArgs{ | ||||
| 		Pkgs: pkgs, | ||||
| 		Opts: opts, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPCServer) Install(args *InstallerExecutorInstallArgs, resp *InstallerExecutorInstallResp) error { | ||||
| 	err := s.Impl.Install(context.Background(), args.Pkgs, args.Opts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = InstallerExecutorInstallResp{} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRemoveArgs struct { | ||||
| 	Pkgs []string | ||||
| 	Opts *manager.Opts | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRemoveResp struct { | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPC) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||
| 	var resp *InstallerExecutorRemoveResp | ||||
| 	err := s.client.Call("Plugin.Remove", &InstallerExecutorRemoveArgs{ | ||||
| 		Pkgs: pkgs, | ||||
| 		Opts: opts, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPCServer) Remove(args *InstallerExecutorRemoveArgs, resp *InstallerExecutorRemoveResp) error { | ||||
| 	err := s.Impl.Remove(context.Background(), args.Pkgs, args.Opts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = InstallerExecutorRemoveResp{} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRemoveAlreadyInstalledArgs struct { | ||||
| 	Pkgs []string | ||||
| } | ||||
|  | ||||
| type InstallerExecutorRemoveAlreadyInstalledResp struct { | ||||
| 	Result0 []string | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPC) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) { | ||||
| 	var resp *InstallerExecutorRemoveAlreadyInstalledResp | ||||
| 	err := s.client.Call("Plugin.RemoveAlreadyInstalled", &InstallerExecutorRemoveAlreadyInstalledArgs{ | ||||
| 		Pkgs: pkgs, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp.Result0, nil | ||||
| } | ||||
|  | ||||
| func (s *InstallerExecutorRPCServer) RemoveAlreadyInstalled(args *InstallerExecutorRemoveAlreadyInstalledArgs, resp *InstallerExecutorRemoveAlreadyInstalledResp) error { | ||||
| 	result0, err := s.Impl.RemoveAlreadyInstalled(context.Background(), args.Pkgs) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = InstallerExecutorRemoveAlreadyInstalledResp{ | ||||
| 		Result0: result0, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorReadScriptArgs struct { | ||||
| 	ScriptPath string | ||||
| } | ||||
|  | ||||
| type ScriptExecutorReadScriptResp struct { | ||||
| 	Result0 *alrsh.ScriptFile | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) { | ||||
| 	var resp *ScriptExecutorReadScriptResp | ||||
| 	err := s.client.Call("Plugin.ReadScript", &ScriptExecutorReadScriptArgs{ | ||||
| 		ScriptPath: scriptPath, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp.Result0, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ReadScript(args *ScriptExecutorReadScriptArgs, resp *ScriptExecutorReadScriptResp) error { | ||||
| 	result0, err := s.Impl.ReadScript(context.Background(), args.ScriptPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ScriptExecutorReadScriptResp{ | ||||
| 		Result0: result0, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorExecuteFirstPassArgs struct { | ||||
| 	Input *BuildInput | ||||
| 	Sf    *alrsh.ScriptFile | ||||
| } | ||||
|  | ||||
| type ScriptExecutorExecuteFirstPassResp struct { | ||||
| 	Result0 string | ||||
| 	Result1 []*alrsh.Package | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) { | ||||
| 	var resp *ScriptExecutorExecuteFirstPassResp | ||||
| 	err := s.client.Call("Plugin.ExecuteFirstPass", &ScriptExecutorExecuteFirstPassArgs{ | ||||
| 		Input: input, | ||||
| 		Sf:    sf, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return resp.Result0, resp.Result1, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ScriptExecutorExecuteFirstPassArgs, resp *ScriptExecutorExecuteFirstPassResp) error { | ||||
| 	result0, result1, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ScriptExecutorExecuteFirstPassResp{ | ||||
| 		Result0: result0, | ||||
| 		Result1: result1, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorPrepareDirsArgs struct { | ||||
| 	Input   *BuildInput | ||||
| 	BasePkg string | ||||
| } | ||||
|  | ||||
| type ScriptExecutorPrepareDirsResp struct { | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) PrepareDirs(ctx context.Context, input *BuildInput, basePkg string) error { | ||||
| 	var resp *ScriptExecutorPrepareDirsResp | ||||
| 	err := s.client.Call("Plugin.PrepareDirs", &ScriptExecutorPrepareDirsArgs{ | ||||
| 		Input:   input, | ||||
| 		BasePkg: basePkg, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) PrepareDirs(args *ScriptExecutorPrepareDirsArgs, resp *ScriptExecutorPrepareDirsResp) error { | ||||
| 	err := s.Impl.PrepareDirs(context.Background(), args.Input, args.BasePkg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ScriptExecutorPrepareDirsResp{} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorExecuteSecondPassArgs struct { | ||||
| 	Input          *BuildInput | ||||
| 	Sf             *alrsh.ScriptFile | ||||
| 	VarsOfPackages []*alrsh.Package | ||||
| 	RepoDeps       []string | ||||
| 	BuiltDeps      []*BuiltDep | ||||
| 	BasePkg        string | ||||
| } | ||||
|  | ||||
| type ScriptExecutorExecuteSecondPassResp struct { | ||||
| 	Result0 []*BuiltDep | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteSecondPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, varsOfPackages []*alrsh.Package, repoDeps []string, builtDeps []*BuiltDep, basePkg string) ([]*BuiltDep, error) { | ||||
| 	var resp *ScriptExecutorExecuteSecondPassResp | ||||
| 	err := s.client.Call("Plugin.ExecuteSecondPass", &ScriptExecutorExecuteSecondPassArgs{ | ||||
| 		Input:          input, | ||||
| 		Sf:             sf, | ||||
| 		VarsOfPackages: varsOfPackages, | ||||
| 		RepoDeps:       repoDeps, | ||||
| 		BuiltDeps:      builtDeps, | ||||
| 		BasePkg:        basePkg, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp.Result0, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ScriptExecutorExecuteSecondPassArgs, resp *ScriptExecutorExecuteSecondPassResp) error { | ||||
| 	result0, err := s.Impl.ExecuteSecondPass(context.Background(), args.Input, args.Sf, args.VarsOfPackages, args.RepoDeps, args.BuiltDeps, args.BasePkg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ScriptExecutorExecuteSecondPassResp{ | ||||
| 		Result0: result0, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ReposExecutorPullOneAndUpdateFromConfigArgs struct { | ||||
| 	Repo *types.Repo | ||||
| } | ||||
|  | ||||
| type ReposExecutorPullOneAndUpdateFromConfigResp struct { | ||||
| 	Result0 types.Repo | ||||
| } | ||||
|  | ||||
| func (s *ReposExecutorRPC) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) { | ||||
| 	var resp *ReposExecutorPullOneAndUpdateFromConfigResp | ||||
| 	err := s.client.Call("Plugin.PullOneAndUpdateFromConfig", &ReposExecutorPullOneAndUpdateFromConfigArgs{ | ||||
| 		Repo: repo, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return types.Repo{}, err | ||||
| 	} | ||||
| 	return resp.Result0, nil | ||||
| } | ||||
|  | ||||
| func (s *ReposExecutorRPCServer) PullOneAndUpdateFromConfig(args *ReposExecutorPullOneAndUpdateFromConfigArgs, resp *ReposExecutorPullOneAndUpdateFromConfigResp) error { | ||||
| 	result0, err := s.Impl.PullOneAndUpdateFromConfig(context.Background(), args.Repo) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ReposExecutorPullOneAndUpdateFromConfigResp{ | ||||
| 		Result0: result0, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -17,24 +17,21 @@ | ||||
| package build | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| ) | ||||
| 
 | ||||
| func setCommonCmdEnv(cmd *exec.Cmd) { | ||||
| 	cmd.Env = []string{ | ||||
| 		"HOME=/var/cache/alr", | ||||
| 		"LOGNAME=alr", | ||||
| 		"USER=alr", | ||||
| 		"PATH=/usr/bin:/bin:/usr/local/bin", | ||||
| 	} | ||||
| 	for _, env := range os.Environ() { | ||||
| 		if strings.HasPrefix(env, "LANG=") || | ||||
| 			strings.HasPrefix(env, "LANGUAGE=") || | ||||
| 			strings.HasPrefix(env, "LC_") || | ||||
| 			strings.HasPrefix(env, "ALR_LOG_LEVEL=") { | ||||
| 			cmd.Env = append(cmd.Env, env) | ||||
| 		} | ||||
| 	} | ||||
| type reposExecutor struct{ r *repos.Repos } | ||||
| 
 | ||||
| func NewRepos(r *repos.Repos) ReposExecutor { | ||||
| 	return &reposExecutor{r} | ||||
| } | ||||
| 
 | ||||
| func (r *reposExecutor) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) { | ||||
| 	if err := r.r.PullOneAndUpdateFromConfig(ctx, repo); err != nil { | ||||
| 		return *repo, err | ||||
| 	} | ||||
| 	return *repo, nil | ||||
| } | ||||
| @@ -1,161 +0,0 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"net/rpc" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||
| ) | ||||
|  | ||||
| type InstallerPlugin struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallerRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| type InstallerRPCServer struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallArgs struct { | ||||
| 	PackagesOrPaths []string | ||||
| 	Opts            *manager.Opts | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error { | ||||
| 	return r.client.Call("Plugin.InstallLocal", &InstallArgs{ | ||||
| 		PackagesOrPaths: paths, | ||||
| 		Opts:            opts, | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error { | ||||
| 	return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts) | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error { | ||||
| 	return r.client.Call("Plugin.Install", &InstallArgs{ | ||||
| 		PackagesOrPaths: pkgs, | ||||
| 		Opts:            opts, | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error { | ||||
| 	return s.Impl.Install(args.PackagesOrPaths, args.Opts) | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) Remove(pkgs []string, opts *manager.Opts) error { | ||||
| 	return r.client.Call("Plugin.Remove", &InstallArgs{ | ||||
| 		PackagesOrPaths: pkgs, | ||||
| 		Opts:            opts, | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) Remove(args *InstallArgs, reply *struct{}) error { | ||||
| 	return s.Impl.Remove(args.PackagesOrPaths, args.Opts) | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) { | ||||
| 	var val []string | ||||
| 	err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val) | ||||
| 	return val, err | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error { | ||||
| 	vars, err := s.Impl.RemoveAlreadyInstalled(pkgs) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*res = vars | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &InstallerRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &InstallerRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| func GetSafeInstaller() (InstallerExecutor, func(), error) { | ||||
| 	var err error | ||||
|  | ||||
| 	executable, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	cmd := exec.Command(executable, "_internal-installer") | ||||
| 	setCommonCmdEnv(cmd) | ||||
|  | ||||
| 	slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||
|  | ||||
| 	client := plugin.NewClient(&plugin.ClientConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Plugins:         pluginMap, | ||||
| 		Cmd:             cmd, | ||||
| 		Logger:          logger.GetHCLoggerAdapter(), | ||||
| 		SkipHostEnv:     true, | ||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | ||||
| 			Group: "alr", | ||||
| 		}, | ||||
| 		SyncStderr: os.Stderr, | ||||
| 	}) | ||||
| 	rpcClient, err := client.Client() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var cleanupOnce sync.Once | ||||
| 	cleanup := func() { | ||||
| 		cleanupOnce.Do(func() { | ||||
| 			client.Kill() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			slog.Debug("close installer") | ||||
| 			cleanup() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	raw, err := rpcClient.Dispense("installer") | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	executor, ok := raw.(InstallerExecutor) | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw) | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return executor, cleanup, nil | ||||
| } | ||||
| @@ -1,273 +0,0 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"net/rpc" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||
| ) | ||||
|  | ||||
| var HandshakeConfig = plugin.HandshakeConfig{ | ||||
| 	ProtocolVersion:  1, | ||||
| 	MagicCookieKey:   "ALR_PLUGIN", | ||||
| 	MagicCookieValue: "-", | ||||
| } | ||||
|  | ||||
| type ScriptExecutorPlugin struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPCServer struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ReadScript | ||||
| // | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) { | ||||
| 	var resp *alrsh.ScriptFile | ||||
| 	err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) | ||||
| 	return resp, err | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error { | ||||
| 	file, err := s.Impl.ReadScript(context.Background(), scriptPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = *file | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ExecuteFirstPass | ||||
| // | ||||
|  | ||||
| type ExecuteFirstPassArgs struct { | ||||
| 	Input *BuildInput | ||||
| 	Sf    *alrsh.ScriptFile | ||||
| } | ||||
|  | ||||
| type ExecuteFirstPassResp struct { | ||||
| 	BasePkg        string | ||||
| 	VarsOfPackages []*alrsh.Package | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) { | ||||
| 	var resp *ExecuteFirstPassResp | ||||
| 	err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ | ||||
| 		Input: input, | ||||
| 		Sf:    sf, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return resp.BasePkg, resp.VarsOfPackages, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error { | ||||
| 	basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ExecuteFirstPassResp{ | ||||
| 		BasePkg:        basePkg, | ||||
| 		VarsOfPackages: varsOfPackages, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // PrepareDirs | ||||
| // | ||||
|  | ||||
| type PrepareDirsArgs struct { | ||||
| 	Input   *BuildInput | ||||
| 	BasePkg string | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) PrepareDirs( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	basePkg string, | ||||
| ) error { | ||||
| 	err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{ | ||||
| 		Input:   input, | ||||
| 		BasePkg: basePkg, | ||||
| 	}, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error { | ||||
| 	err := s.Impl.PrepareDirs( | ||||
| 		context.Background(), | ||||
| 		args.Input, | ||||
| 		args.BasePkg, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ExecuteSecondPass | ||||
| // | ||||
|  | ||||
| type ExecuteSecondPassArgs struct { | ||||
| 	Input          *BuildInput | ||||
| 	Sf             *alrsh.ScriptFile | ||||
| 	VarsOfPackages []*alrsh.Package | ||||
| 	RepoDeps       []string | ||||
| 	BuiltDeps      []*BuiltDep | ||||
| 	BasePkg        string | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteSecondPass( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	sf *alrsh.ScriptFile, | ||||
| 	varsOfPackages []*alrsh.Package, | ||||
| 	repoDeps []string, | ||||
| 	builtDeps []*BuiltDep, | ||||
| 	basePkg string, | ||||
| ) ([]*BuiltDep, error) { | ||||
| 	var resp []*BuiltDep | ||||
| 	err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{ | ||||
| 		Input:          input, | ||||
| 		Sf:             sf, | ||||
| 		VarsOfPackages: varsOfPackages, | ||||
| 		RepoDeps:       repoDeps, | ||||
| 		BuiltDeps:      builtDeps, | ||||
| 		BasePkg:        basePkg, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error { | ||||
| 	res, err := s.Impl.ExecuteSecondPass( | ||||
| 		context.Background(), | ||||
| 		args.Input, | ||||
| 		args.Sf, | ||||
| 		args.VarsOfPackages, | ||||
| 		args.RepoDeps, | ||||
| 		args.BuiltDeps, | ||||
| 		args.BasePkg, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = res | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // | ||||
| // ============================ | ||||
| // | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| var pluginMap = map[string]plugin.Plugin{ | ||||
| 	"script-executor": &ScriptExecutorPlugin{}, | ||||
| 	"installer":       &InstallerPlugin{}, | ||||
| } | ||||
|  | ||||
| func GetSafeScriptExecutor() (ScriptExecutor, func(), error) { | ||||
| 	var err error | ||||
|  | ||||
| 	executable, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command(executable, "_internal-safe-script-executor") | ||||
| 	setCommonCmdEnv(cmd) | ||||
|  | ||||
| 	client := plugin.NewClient(&plugin.ClientConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Plugins:         pluginMap, | ||||
| 		Cmd:             cmd, | ||||
| 		Logger:          logger.GetHCLoggerAdapter(), | ||||
| 		SkipHostEnv:     true, | ||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | ||||
| 			Group: "alr", | ||||
| 		}, | ||||
| 		SyncStderr: os.Stderr, | ||||
| 	}) | ||||
| 	rpcClient, err := client.Client() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var cleanupOnce sync.Once | ||||
| 	cleanup := func() { | ||||
| 		cleanupOnce.Do(func() { | ||||
| 			client.Kill() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			slog.Debug("close script-executor") | ||||
| 			cleanup() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	raw, err := rpcClient.Dispense("script-executor") | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	executor, ok := raw.(ScriptExecutor) | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw) | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return executor, cleanup, nil | ||||
| } | ||||
| @@ -68,7 +68,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range repos { | ||||
| 		err := rs.pullRepo(ctx, repo) | ||||
| 		err := rs.pullRepo(ctx, &repo, false) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -77,7 +77,16 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error { | ||||
| func (rs *Repos) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) error { | ||||
| 	err := rs.pullRepo(ctx, repo, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (rs *Repos) pullRepo(ctx context.Context, repo *types.Repo, updateRepoFromToml bool) error { | ||||
| 	urls := []string{repo.URL} | ||||
| 	urls = append(urls, repo.Mirrors...) | ||||
|  | ||||
| @@ -88,7 +97,7 @@ func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error { | ||||
| 			slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL) | ||||
| 		} | ||||
|  | ||||
| 		err := rs.pullRepoFromURL(ctx, repoURL, repo) | ||||
| 		err := rs.pullRepoFromURL(ctx, repoURL, repo, updateRepoFromToml) | ||||
| 		if err != nil { | ||||
| 			lastErr = err | ||||
| 			slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err) | ||||
| @@ -149,7 +158,7 @@ func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) { | ||||
| 	return r, true, nil | ||||
| } | ||||
|  | ||||
| func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error { | ||||
| func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *types.Repo, update bool) error { | ||||
| 	repoURL, err := url.Parse(rawRepoUrl) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err) | ||||
| @@ -214,12 +223,12 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo ty | ||||
| 	// empty. In this case, we need to update the DB fully | ||||
| 	// rather than just incrementally. | ||||
| 	if rs.db.IsEmpty() || freshGit { | ||||
| 		err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 		err = rs.processRepoFull(ctx, *repo, repoDir) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		err = rs.processRepoChanges(ctx, repo, r, w, old, new) | ||||
| 		err = rs.processRepoChanges(ctx, *repo, r, w, old, new) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -247,6 +256,18 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo ty | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if update { | ||||
| 		if repoCfg.Repo.URL != "" { | ||||
| 			repo.URL = repoCfg.Repo.URL | ||||
| 		} | ||||
| 		if repoCfg.Repo.Ref != "" { | ||||
| 			repo.Ref = repoCfg.Repo.Ref | ||||
| 		} | ||||
| 		if len(repoCfg.Repo.Mirrors) > 0 { | ||||
| 			repo.Mirrors = repoCfg.Repo.Mirrors | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -218,23 +218,23 @@ msgstr "" | ||||
| msgid "Error removing packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/build.go:378 | ||||
| #: internal/build/build.go:351 | ||||
| msgid "Building package" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/build.go:407 | ||||
| #: internal/build/build.go:380 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/build.go:449 | ||||
| #: internal/build/build.go:422 | ||||
| msgid "Downloading sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/build.go:495 | ||||
| #: internal/build/build.go:468 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/build.go:571 | ||||
| #: internal/build/build.go:546 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -407,27 +407,27 @@ msgstr "" | ||||
| msgid "ERROR" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:88 | ||||
| #: internal/repos/pull.go:97 | ||||
| msgid "Trying mirror" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:94 | ||||
| #: internal/repos/pull.go:103 | ||||
| msgid "Failed to pull from URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:158 | ||||
| #: internal/repos/pull.go:167 | ||||
| msgid "Pulling repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:195 | ||||
| #: internal/repos/pull.go:204 | ||||
| msgid "Repository up to date" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:230 | ||||
| #: internal/repos/pull.go:239 | ||||
| msgid "Git repository does not appear to be a valid ALR repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:246 | ||||
| #: internal/repos/pull.go:255 | ||||
| msgid "" | ||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | ||||
| "updating ALR if something doesn't work." | ||||
| @@ -481,11 +481,11 @@ msgstr "" | ||||
| msgid "Enable interactive questions and prompts" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:147 | ||||
| #: main.go:148 | ||||
| msgid "Show help" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:151 | ||||
| #: main.go:152 | ||||
| msgid "Error while running app" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -517,44 +517,44 @@ msgstr "" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:41 | ||||
| #: repo.go:42 | ||||
| msgid "Manage repos" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:55 repo.go:625 | ||||
| #: repo.go:56 repo.go:625 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:57 repo.go:521 | ||||
| #: repo.go:58 repo.go:521 | ||||
| msgid "<name>" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:102 repo.go:465 repo.go:568 | ||||
| #: repo.go:103 repo.go:465 repo.go:568 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:109 | ||||
| #: repo.go:110 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:576 | ||||
| msgid "Error saving config" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:132 | ||||
| #: repo.go:133 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:143 repo.go:595 | ||||
| #: repo.go:144 repo.go:595 | ||||
| msgid "Add a new repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||
| #: repo.go:145 repo.go:270 repo.go:345 repo.go:402 | ||||
| msgid "<name> <url>" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:169 | ||||
| #: repo.go:170 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -225,23 +225,23 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум | ||||
| msgid "Error removing packages" | ||||
| msgstr "Ошибка при удалении пакетов" | ||||
|  | ||||
| #: internal/build/build.go:378 | ||||
| #: internal/build/build.go:351 | ||||
| msgid "Building package" | ||||
| msgstr "Сборка пакета" | ||||
|  | ||||
| #: internal/build/build.go:407 | ||||
| #: internal/build/build.go:380 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | ||||
|  | ||||
| #: internal/build/build.go:449 | ||||
| #: internal/build/build.go:422 | ||||
| msgid "Downloading sources" | ||||
| msgstr "Скачивание источников" | ||||
|  | ||||
| #: internal/build/build.go:495 | ||||
| #: internal/build/build.go:468 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "Хотели бы вы удалить зависимости сборки?" | ||||
|  | ||||
| #: internal/build/build.go:571 | ||||
| #: internal/build/build.go:546 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "Установка зависимостей" | ||||
|  | ||||
| @@ -421,27 +421,27 @@ msgstr "" | ||||
| msgid "ERROR" | ||||
| msgstr "ОШИБКА" | ||||
|  | ||||
| #: internal/repos/pull.go:88 | ||||
| #: internal/repos/pull.go:97 | ||||
| msgid "Trying mirror" | ||||
| msgstr "Пробую зеркало" | ||||
|  | ||||
| #: internal/repos/pull.go:94 | ||||
| #: internal/repos/pull.go:103 | ||||
| msgid "Failed to pull from URL" | ||||
| msgstr "Не удалось извлечь из URL" | ||||
|  | ||||
| #: internal/repos/pull.go:158 | ||||
| #: internal/repos/pull.go:167 | ||||
| msgid "Pulling repository" | ||||
| msgstr "Скачивание репозитория" | ||||
|  | ||||
| #: internal/repos/pull.go:195 | ||||
| #: internal/repos/pull.go:204 | ||||
| msgid "Repository up to date" | ||||
| msgstr "Репозиторий уже обновлён" | ||||
|  | ||||
| #: internal/repos/pull.go:230 | ||||
| #: internal/repos/pull.go:239 | ||||
| msgid "Git repository does not appear to be a valid ALR repo" | ||||
| msgstr "Репозиторий Git не поддерживается репозиторием ALR" | ||||
|  | ||||
| #: internal/repos/pull.go:246 | ||||
| #: internal/repos/pull.go:255 | ||||
| msgid "" | ||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | ||||
| "updating ALR if something doesn't work." | ||||
| @@ -497,11 +497,11 @@ msgstr "Аргументы, которые будут переданы мене | ||||
| msgid "Enable interactive questions and prompts" | ||||
| msgstr "Включение интерактивных вопросов и запросов" | ||||
|  | ||||
| #: main.go:147 | ||||
| #: main.go:148 | ||||
| msgid "Show help" | ||||
| msgstr "Показать справку" | ||||
|  | ||||
| #: main.go:151 | ||||
| #: main.go:152 | ||||
| msgid "Error while running app" | ||||
| msgstr "Ошибка при запуске приложения" | ||||
|  | ||||
| @@ -533,44 +533,44 @@ msgstr "%s %s загружается — %s/с\n" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "Скачать все изменённые репозитории" | ||||
|  | ||||
| #: repo.go:41 | ||||
| #: repo.go:42 | ||||
| msgid "Manage repos" | ||||
| msgstr "Управление репозиториями" | ||||
|  | ||||
| #: repo.go:55 repo.go:625 | ||||
| #: repo.go:56 repo.go:625 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "Удалить существующий репозиторий" | ||||
|  | ||||
| #: repo.go:57 repo.go:521 | ||||
| #: repo.go:58 repo.go:521 | ||||
| msgid "<name>" | ||||
| msgstr "<имя>" | ||||
|  | ||||
| #: repo.go:102 repo.go:465 repo.go:568 | ||||
| #: repo.go:103 repo.go:465 repo.go:568 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "Репозитория \"%s\" не существует" | ||||
|  | ||||
| #: repo.go:109 | ||||
| #: repo.go:110 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "Ошибка при удалении каталога репозитория" | ||||
|  | ||||
| #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:576 | ||||
| msgid "Error saving config" | ||||
| msgstr "Ошибка при сохранении конфигурации" | ||||
|  | ||||
| #: repo.go:132 | ||||
| #: repo.go:133 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "Ошибка при удалении пакетов из базы данных" | ||||
|  | ||||
| #: repo.go:143 repo.go:595 | ||||
| #: repo.go:144 repo.go:595 | ||||
| msgid "Add a new repository" | ||||
| msgstr "Добавить новый репозиторий" | ||||
|  | ||||
| #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||
| #: repo.go:145 repo.go:270 repo.go:345 repo.go:402 | ||||
| msgid "<name> <url>" | ||||
| msgstr "<имя> <url>" | ||||
|  | ||||
| #: repo.go:169 | ||||
| #: repo.go:170 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "Репозиторий \"%s\" уже существует" | ||||
|  | ||||
|   | ||||
| @@ -131,11 +131,11 @@ func EnsureIsAlrUser() error { | ||||
| 	} | ||||
| 	newUid := syscall.Getuid() | ||||
| 	if newUid != uid { | ||||
| 		return errors.New("new uid don't matches requested") | ||||
| 		return errors.New("uid don't matches requested") | ||||
| 	} | ||||
| 	newGid := syscall.Getgid() | ||||
| 	if newGid != gid { | ||||
| 		return errors.New("new gid don't matches requested") | ||||
| 		return errors.New("gid don't matches requested") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							| @@ -88,6 +88,7 @@ func GetApp() *cli.App { | ||||
| 			InternalBuildCmd(), | ||||
| 			InternalInstallCmd(), | ||||
| 			InternalMountCmd(), | ||||
| 			InternalReposCmd(), | ||||
| 		}, | ||||
| 		Before: func(c *cli.Context) error { | ||||
| 			if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ package dl | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| @@ -149,6 +150,7 @@ func (d *GitDownloader) Update(opts Options) (bool, error) { | ||||
| 	u.Scheme = strings.TrimPrefix(u.Scheme, "git+") | ||||
|  | ||||
| 	query := u.Query() | ||||
| 	rev := query.Get("~rev") | ||||
| 	query.Del("~rev") | ||||
|  | ||||
| 	depthStr := query.Get("~depth") | ||||
| @@ -177,27 +179,67 @@ func (d *GitDownloader) Update(opts Options) (bool, error) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	po := &git.PullOptions{ | ||||
| 		Depth:             depth, | ||||
| 		Progress:          opts.Progress, | ||||
| 		RecurseSubmodules: git.NoRecurseSubmodules, | ||||
| 	} | ||||
|  | ||||
| 	if recursive == "true" { | ||||
| 		po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth | ||||
| 	// First, we do a fetch to get all the revisions. | ||||
| 	fo := &git.FetchOptions{ | ||||
| 		Depth:    depth, | ||||
| 		Progress: opts.Progress, | ||||
| 	} | ||||
|  | ||||
| 	m, err := getManifest(opts.Destination) | ||||
| 	manifestOK := err == nil | ||||
|  | ||||
| 	err = w.Pull(po) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 	err = r.Fetch(fo) | ||||
| 	if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	// If a revision is specified, switch to it. | ||||
| 	if rev != "" { | ||||
| 		// We are trying to find the revision as a hash of the commit | ||||
| 		hash, err := r.ResolveRevision(plumbing.Revision(rev)) | ||||
| 		if err != nil { | ||||
| 			return false, fmt.Errorf("failed to resolve revision %s: %w", rev, err) | ||||
| 		} | ||||
|  | ||||
| 		err = w.Checkout(&git.CheckoutOptions{ | ||||
| 			Hash: *hash, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return false, fmt.Errorf("failed to checkout revision %s: %w", rev, err) | ||||
| 		} | ||||
|  | ||||
| 		if recursive == "true" { | ||||
| 			submodules, err := w.Submodules() | ||||
| 			if err == nil { | ||||
| 				err = submodules.Update(&git.SubmoduleUpdateOptions{ | ||||
| 					Init: true, | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					return false, fmt.Errorf("failed to update submodules %s: %w", rev, err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		// If the revision is not specified, we do a regular pull. | ||||
| 		po := &git.PullOptions{ | ||||
| 			Depth:             depth, | ||||
| 			Progress:          opts.Progress, | ||||
| 			RecurseSubmodules: git.NoRecurseSubmodules, | ||||
| 		} | ||||
|  | ||||
| 		if recursive == "true" { | ||||
| 			po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth | ||||
| 		} | ||||
|  | ||||
| 		err = w.Pull(po) | ||||
| 		if err != nil { | ||||
| 			if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 				return false, nil | ||||
| 			} | ||||
| 			return false, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = VerifyHashFromLocal("", opts) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
|   | ||||
| @@ -153,7 +153,7 @@ func TestGitDownloaderUpdate(t *testing.T) { | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		updated, err := d.Update(dl.Options{ | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader", | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader", | ||||
| 			Destination:   dest, | ||||
| 			Hash:          hsh, | ||||
| 			HashAlgorithm: "sha256", | ||||
|   | ||||
| @@ -22,6 +22,9 @@ package types | ||||
| // RepoConfig represents a ALR repo's alr-repo.toml file. | ||||
| type RepoConfig struct { | ||||
| 	Repo struct { | ||||
| 		MinVersion string `toml:"minVersion"` | ||||
| 		MinVersion string   `toml:"minVersion"` | ||||
| 		URL        string   `toml:"url"` | ||||
| 		Ref        string   `toml:"ref"` | ||||
| 		Mirrors    []string `toml:"mirrors"` | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										34
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								repo.go
									
									
									
									
									
								
							| @@ -29,6 +29,7 @@ import ( | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"golang.org/x/exp/slices" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| @@ -169,10 +170,24 @@ func AddRepoCmd() *cli.Command { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil) | ||||
| 				} | ||||
| 			} | ||||
| 			reposSlice = append(reposSlice, types.Repo{ | ||||
|  | ||||
| 			newRepo := types.Repo{ | ||||
| 				Name: name, | ||||
| 				URL:  repoURL, | ||||
| 			}) | ||||
| 			} | ||||
|  | ||||
| 			r, close, err := build.GetSafeReposExecutor() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer close() | ||||
|  | ||||
| 			newRepo, err = r.PullOneAndUpdateFromConfig(c.Context, &newRepo) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			reposSlice = append(reposSlice, newRepo) | ||||
| 			cfg.SetRepos(reposSlice) | ||||
|  | ||||
| 			err = cfg.System.Save() | ||||
| @@ -180,21 +195,6 @@ func AddRepoCmd() *cli.Command { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			deps, err = appbuilder. | ||||
| 				New(ctx). | ||||
| 				UseConfig(cfg). | ||||
| 				WithDB(). | ||||
| 				WithReposForcePull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user