forked from Plemya-x/ALR
		
	refactor: generate plugin executors
This commit is contained in:
		| @@ -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 | 
							
								
								
									
										390
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,390 @@ | ||||
| // 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" | ||||
|  | ||||
| 	"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 { | ||||
| 			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" | ||||
| 			default: | ||||
| 				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) | ||||
| 	} | ||||
| } | ||||
| @@ -125,7 +125,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 { | ||||
|   | ||||
							
								
								
									
										125
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| // 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-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| ) | ||||
|  | ||||
| var pluginMap = map[string]plugin.Plugin{ | ||||
| 	"script-executor": &ScriptExecutorPlugin{}, | ||||
| 	"installer":       &InstallerExecutorPlugin{}, | ||||
| } | ||||
|  | ||||
| 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 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 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 | ||||
| } | ||||
							
								
								
									
										55
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| //go:generate go run ../../generators/plugin-generator InstallerExecutor ScriptExecutor | ||||
|  | ||||
| // 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) | ||||
| } | ||||
							
								
								
									
										318
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										318
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,318 @@ | ||||
| // 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" | ||||
| 	"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 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 | ||||
| } | ||||
| @@ -1,40 +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 ( | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| 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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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 "" | ||||
|  | ||||
|   | ||||
| @@ -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 "Установка зависимостей" | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user