feat: remove NWScript compiler support

This commit is contained in:
2026-06-09 17:57:57 +02:00
parent de7e99c55c
commit f3ed13ce19
10 changed files with 4 additions and 1297 deletions
-220
View File
@@ -6,11 +6,9 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
@@ -544,228 +542,10 @@ func collectModuleResources(p *project.Project, moduleHakOrder []string) ([]erf.
moduleResources = append(moduleResources, resource)
}
compiledScripts, err := compileReferencedScripts(p)
if err != nil {
return nil, err
}
moduleResources = append(moduleResources, compiledScripts...)
sortResources(moduleResources)
return moduleResources, nil
}
func compileReferencedScripts(p *project.Project) ([]erf.Resource, error) {
if skipNSSCompilation() {
return nil, nil
}
referenced, err := referencedScriptResrefs(p)
if err != nil {
return nil, err
}
if len(referenced) == 0 {
return nil, nil
}
scriptSources := make(map[string]string, len(p.Inventory.ScriptFiles))
for _, rel := range p.Inventory.ScriptFiles {
resref := strings.ToLower(strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)))
scriptSources[resref] = filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
}
compiler, err := resolveScriptCompiler(p)
if err != nil {
return nil, err
}
compilerEnv, err := resolveScriptCompilerEnvironment(p)
if err != nil {
return nil, err
}
cacheDir := compiledScriptCacheDir(p)
if err := os.RemoveAll(cacheDir); err != nil {
return nil, fmt.Errorf("clear script compiler cache dir: %w", err)
}
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
return nil, fmt.Errorf("create script compiler cache dir: %w", err)
}
resources := make([]erf.Resource, 0, len(referenced))
scriptsDir := p.ScriptSourceDir()
for _, resref := range referenced {
sourcePath, ok := scriptSources[resref]
if !ok {
continue
}
outputPath := filepath.Join(cacheDir, resref+".ncs")
cmd := exec.Command(compiler, "--dirs", scriptsDir, "-o", outputPath, sourcePath)
cmd.Env = compilerEnv
output, err := cmd.CombinedOutput()
if err != nil {
message := strings.TrimSpace(string(output))
if message == "" {
message = err.Error()
}
return nil, fmt.Errorf("compile script %s: %s", filepath.Base(sourcePath), message)
}
resource, err := rawResource(outputPath)
if err != nil {
return nil, fmt.Errorf("load compiled script %s: %w", filepath.Base(outputPath), err)
}
resources = append(resources, resource)
}
return resources, nil
}
func skipNSSCompilation() bool {
switch strings.ToLower(strings.TrimSpace(os.Getenv("SOW_MODULE_SKIP_NSS_COMPILATION"))) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
func compiledScriptCacheDir(p *project.Project) string {
return p.ScriptCacheDir()
}
func referencedScriptResrefs(p *project.Project) ([]string, error) {
referenced := map[string]struct{}{}
for _, rel := range p.Inventory.SourceFiles {
abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
document, _, _, err := validatorLoadDocument(abs)
if err != nil {
return nil, err
}
validatorWalkFields(document.Root, func(fieldLabel, value string) {
if !validatorIsScriptField(fieldLabel) {
return
}
value = strings.TrimSpace(value)
if value == "" {
return
}
referenced[strings.ToLower(value)] = struct{}{}
})
}
resrefs := make([]string, 0, len(referenced))
for resref := range referenced {
resrefs = append(resrefs, resref)
}
slices.Sort(resrefs)
return resrefs, nil
}
func resolveScriptCompiler(p *project.Project) (string, error) {
candidates := make([]string, 0, 2+len(p.ScriptCompilerSearchPaths()))
if configured := p.ScriptCompilerPath(); configured != "" {
candidates = append(candidates, configured)
}
if configured := strings.TrimSpace(os.Getenv(p.ScriptCompilerPathEnv())); configured != "" {
candidates = append(candidates, configured)
}
candidates = append(candidates, p.ScriptCompilerSearchPaths()...)
if runtime.GOOS == "windows" {
slices.SortStableFunc(candidates, func(a, b string) int {
aExe := strings.HasSuffix(strings.ToLower(a), ".exe")
bExe := strings.HasSuffix(strings.ToLower(b), ".exe")
if aExe == bExe {
return strings.Compare(a, b)
}
if aExe {
return -1
}
return 1
})
}
for _, candidate := range candidates {
if binary, ok := strings.CutPrefix(candidate, "PATH:"); ok {
if compiler, err := exec.LookPath(binary); err == nil && scriptCompilerRunnable(compiler) {
return compiler, nil
}
continue
}
if info, err := os.Stat(candidate); err == nil && !info.IsDir() && scriptCompilerRunnable(candidate) {
return candidate, nil
}
}
return "", fmt.Errorf("script compiler not found; configure scripts.compiler.path or set %s", p.ScriptCompilerPathEnv())
}
func scriptCompilerRunnable(path string) bool {
if strings.TrimSpace(path) == "" {
return false
}
info, err := os.Stat(path)
if err != nil || info.IsDir() {
return false
}
cmd := exec.Command(path, "--help")
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
return cmd.Run() == nil
}
func validatorLoadDocument(path string) (gff.Document, string, string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return gff.Document{}, "", "", fmt.Errorf("read %s: %w", path, err)
}
stem := strings.TrimSuffix(filepath.Base(path), ".json")
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(stem)), ".")
if extension == "" {
return gff.Document{}, "", "", fmt.Errorf("missing resource extension before .json: %s", path)
}
resref := strings.ToLower(strings.TrimSuffix(stem, "."+extension))
var document gff.Document
if err := json.Unmarshal(raw, &document); err != nil {
return gff.Document{}, "", "", fmt.Errorf("parse %s: %w", path, err)
}
return document, resref, extension, nil
}
func validatorWalkFields(root gff.Struct, visit func(string, string)) {
walkScriptFields(root, visit)
}
func validatorIsScriptField(label string) bool {
return strings.HasPrefix(label, "On") || strings.HasSuffix(label, "Script") || strings.Contains(label, "Script")
}
func walkScriptFields(s gff.Struct, visit func(string, string)) {
for _, field := range s.Fields {
if value, ok := fieldStringValueForScripts(field.Value); ok {
visit(field.Label, value)
}
switch typed := field.Value.(type) {
case gff.Struct:
walkScriptFields(typed, visit)
case gff.ListValue:
for _, item := range typed {
walkScriptFields(item, visit)
}
}
}
}
func fieldStringValueForScripts(value gff.Value) (string, bool) {
switch typed := value.(type) {
case gff.StringValue:
return string(typed), true
case gff.ResRefValue:
return string(typed), true
default:
return "", false
}
}
func collectAssetResources(p *project.Project, requireContent bool, allowed map[string]struct{}, musicAssets *preparedMusicAssets, generated2DAAssets []topdata.Generated2DAAsset) ([]assetResource, error) {
var hakResources []assetResource
assetCreatedAt, err := collectGitAssetCreationTimes(p)