diff --git a/internal/app/app.go b/internal/app/app.go index 2448759..01b5ca8 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -355,9 +355,8 @@ func runBuildModule(ctx context) error { } type buildModulePreflightResult struct { - ManifestStatus string - ManifestPath string - ScriptCompilationStatus string + ManifestStatus string + ManifestPath string } func runBuildModulePreflight(ctx context, p *project.Project, progress func(string)) (buildModulePreflightResult, error) { @@ -371,11 +370,6 @@ func runBuildModulePreflight(ctx context, p *project.Project, progress func(stri } result.ManifestStatus = manifestStatus result.ManifestPath = manifestPath - scriptStatus, err := prepareBuildModuleCompiler(ctx, p, progress) - if err != nil { - return result, err - } - result.ScriptCompilationStatus = scriptStatus return result, nil } @@ -406,117 +400,6 @@ func refreshBuildModuleManifest(ctx context, p *project.Project, progress func(s return "refreshed", manifestPath, nil } -func prepareBuildModuleCompiler(ctx context, p *project.Project, progress func(string)) (string, error) { - if envEnabled("SOW_MODULE_SKIP_NSS_COMPILATION") { - progress("SOW_MODULE_SKIP_NSS_COMPILATION is set; skipping NWScript compiler resolution and NSS compilation for this run.") - return "skipped", nil - } - if !projectHasScriptSources(p) { - return "not-needed", nil - } - - compilerPathEnv := p.ScriptCompilerPathEnv() - explicitCompiler := strings.TrimSpace(os.Getenv(compilerPathEnv)) - if explicitCompiler != "" { - if scriptCompilerRunnable(explicitCompiler) { - return "enabled", nil - } - if fileExists(explicitCompiler) { - return disableBuildModuleCompiler(compilerPathEnv, progress, fmt.Sprintf("Configured NWScript compiler is present but cannot start: %s", explicitCompiler)) - } - } - - if _, ok := findUsableBuildModuleCompiler(p); ok { - return "enabled", nil - } - - installScript := projectScriptPath(p.Root, "scripts", "install-script-compiler") - if !fileExists(installScript) { - return disableBuildModuleCompiler(compilerPathEnv, progress, "NWScript compiler not found locally and automatic installer script is unavailable.") - } - - progress("NWScript compiler not found locally. Installing it now...") - if err := runProjectScript(ctx, p, []string{"scripts", "install-script-compiler"}); err != nil { - return disableBuildModuleCompiler(compilerPathEnv, progress, "Automatic NWScript compiler install failed.") - } - if _, ok := findUsableBuildModuleCompiler(p); ok { - return "installed", nil - } - - return disableBuildModuleCompiler(compilerPathEnv, progress, "Installed NWScript compiler but it still cannot start.") -} - -func disableBuildModuleCompiler(compilerPathEnv string, progress func(string), reason string) (string, error) { - if err := os.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "1"); err != nil { - return "", fmt.Errorf("set SOW_MODULE_SKIP_NSS_COMPILATION: %w", err) - } - progress("Skipping NWScript compilation for this build. " + reason) - if compilerPathEnv != "" { - _ = os.Unsetenv(compilerPathEnv) - } - return "skipped", nil -} - -func projectHasScriptSources(p *project.Project) bool { - for _, rel := range p.Inventory.ScriptFiles { - if strings.EqualFold(filepath.Ext(rel), ".nss") { - return true - } - } - return false -} - -func findUsableBuildModuleCompiler(p *project.Project) (string, bool) { - 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()...) - - for _, candidate := range candidates { - if compiler, ok := resolveRunnableCompilerCandidate(candidate); ok { - return compiler, true - } - } - return "", false -} - -func resolveRunnableCompilerCandidate(candidate string) (string, bool) { - if candidate == "" { - return "", false - } - if binary, ok := strings.CutPrefix(candidate, "PATH:"); ok { - compiler, err := exec.LookPath(binary) - if err != nil { - return "", false - } - if scriptCompilerRunnable(compiler) { - return compiler, true - } - return "", false - } - if !fileExists(candidate) { - return "", false - } - if scriptCompilerRunnable(candidate) { - return candidate, true - } - return "", false -} - -func scriptCompilerRunnable(path string) bool { - if !fileExists(path) { - return false - } - cmd := exec.Command(path, "--help") - cmd.Stdout = io.Discard - cmd.Stderr = io.Discard - return cmd.Run() == nil -} - func runProjectScript(ctx context, p *project.Project, scriptParts []string, args ...string) error { if len(scriptParts) == 0 { return errors.New("project script path is required") @@ -659,10 +542,6 @@ func (c *projectConsole) phaseLabel(message string) string { return "refreshing HAK manifest" case strings.HasPrefix(message, "Using existing hak manifest at "): return "reusing HAK manifest" - case strings.HasPrefix(message, "NWScript compiler not found locally. Installing it now"): - return "installing script compiler" - case strings.HasPrefix(message, "Skipping NWScript compilation for this build."): - return "skipping script compilation" case strings.HasPrefix(message, "Validating project"): return "validating project" case strings.HasPrefix(message, "Resolving module HAK order"): @@ -689,9 +568,6 @@ func (c *projectConsole) emitBuildResult(result pipeline.BuildResult, preflight } fmt.Fprintln(c.stdout) } - if preflight.ScriptCompilationStatus != "" && preflight.ScriptCompilationStatus != "not-needed" { - fmt.Fprintf(c.stdout, "script compilation: %s\n", preflight.ScriptCompilationStatus) - } if result.TopPackageHAK != "" { fmt.Fprintf(c.stdout, "top package hak: %s\n", c.relPath(result.TopPackageHAK)) fmt.Fprintf(c.stdout, "top package tlk: %s\n", c.relPath(result.TopPackageTLK)) @@ -716,9 +592,6 @@ func (c *projectConsole) emitBuildModuleResult(result pipeline.BuildResult, pref } fmt.Fprintln(c.stdout) } - if preflight.ScriptCompilationStatus != "" && preflight.ScriptCompilationStatus != "not-needed" { - fmt.Fprintf(c.stdout, "script compilation: %s\n", preflight.ScriptCompilationStatus) - } } func (c *projectConsole) emitExtractResult(result pipeline.ExtractResult) { diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 5a7fbd6..c8611fd 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -322,169 +322,6 @@ func TestProjectConsoleEmitsRelativePaths(t *testing.T) { } } -func TestRunBuildModuleToolkitPreflightRefreshesManifestAndSkipsBrokenCompiler(t *testing.T) { - root := t.TempDir() - mkdirAll(t, filepath.Join(root, "src", "module")) - mkdirAll(t, filepath.Join(root, "src", "placeables")) - mkdirAll(t, filepath.Join(root, "src", "scripts")) - mkdirAll(t, filepath.Join(root, "build")) - mkdirAll(t, filepath.Join(root, "scripts")) - - writeFile(t, filepath.Join(root, "nwn-tool.yaml"), ` -module: - name: Test Module - resref: testmod -paths: - source: src - build: build -scripts: - compiler: - search: - - tools/script-compiler/nwn_script_comp -`) - writeFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{ - "file_type": "IFO ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - {"label": "Mod_Name", "type": "CExoString", "value": "Test Module"} - ] - } -} -`) - writeFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{ - "file_type": "UTP ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - {"label": "OnUsed", "type": "ResRef", "value": "use_thing"} - ] - } -} -`) - writeFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), "void main() {}\n") - writeFile(t, filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), "#!/usr/bin/env sh\nset -eu\nmkdir -p \"$(dirname \"$1\")\"\nprintf '{\"module_haks\":[\"sow_core\"],\"haks\":[]}\n' >\"$1\"\n") - if err := os.Chmod(filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), 0o755); err != nil { - t.Fatalf("chmod fetch-hak-manifest.sh: %v", err) - } - - badCompiler := filepath.Join(root, "bad-compiler.sh") - writeFile(t, badCompiler, "#!/usr/bin/env sh\nexit 1\n") - if err := os.Chmod(badCompiler, 0o755); err != nil { - t.Fatalf("chmod bad compiler: %v", err) - } - t.Setenv("SOW_NWN_SCRIPT_COMPILER", badCompiler) - t.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "") - - var stdout bytes.Buffer - var stderr bytes.Buffer - ctx := context{ - stdout: &stdout, - stderr: &stderr, - cwd: root, - args: []string{"build-module"}, - } - - if err := runBuildModule(ctx); err != nil { - t.Fatalf("runBuildModule failed: %v", err) - } - - moduleSource := mustReadFile(t, filepath.Join(root, "src", "module", "module.ifo.json")) - if !strings.Contains(moduleSource, "\"Mod_HakList\"") || !strings.Contains(moduleSource, "\"sow_core\"") { - t.Fatalf("expected manifest-applied module source, got %q", moduleSource) - } - if _, err := os.Stat(filepath.Join(root, ".cache", "ncs", "use_thing.ncs")); !os.IsNotExist(err) { - t.Fatalf("expected broken explicit compiler to skip compilation, got err=%v", err) - } - if !strings.Contains(stdout.String(), "script compilation: skipped") { - t.Fatalf("expected skip-compilation summary in output, got %q", stdout.String()) - } - if strings.Contains(stdout.String(), "[build-module]") { - t.Fatalf("did not expect raw build-module progress in normal output, got %q", stdout.String()) - } -} - -func TestRunBuildModuleToolkitPreflightInstallsCompilerWhenNeeded(t *testing.T) { - root := t.TempDir() - mkdirAll(t, filepath.Join(root, "src", "module")) - mkdirAll(t, filepath.Join(root, "src", "placeables")) - mkdirAll(t, filepath.Join(root, "src", "scripts")) - mkdirAll(t, filepath.Join(root, "build")) - mkdirAll(t, filepath.Join(root, "scripts")) - - writeFile(t, filepath.Join(root, "nwn-tool.yaml"), ` -module: - name: Test Module - resref: testmod -paths: - source: src - build: build -scripts: - compiler: - search: - - tools/script-compiler/nwn_script_comp -`) - writeFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{ - "file_type": "IFO ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - {"label": "Mod_Name", "type": "CExoString", "value": "Test Module"} - ] - } -} -`) - writeFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{ - "file_type": "UTP ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - {"label": "OnUsed", "type": "ResRef", "value": "use_thing"} - ] - } -} -`) - writeFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), "void main() {}\n") - writeFile(t, filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), "#!/usr/bin/env sh\nset -eu\nmkdir -p \"$(dirname \"$1\")\"\nprintf '{\"module_haks\":[\"sow_core\"],\"haks\":[]}\n' >\"$1\"\n") - writeFile(t, filepath.Join(root, "scripts", "install-script-compiler.sh"), "#!/usr/bin/env sh\nset -eu\ninstall_dir=\"${SOW_SCRIPT_COMPILER_INSTALL_DIR:-./tools/script-compiler}\"\nmkdir -p \"$install_dir\"\ncat >\"$install_dir/nwn_script_comp\" <<'SH'\n#!/usr/bin/env sh\nset -eu\nif [ \"${1:-}\" = \"--help\" ]; then\n exit 0\nfi\nout=\"\"\nsrc=\"\"\nwhile [ \"$#\" -gt 0 ]; do\n case \"$1\" in\n -o)\n out=\"$2\"\n shift 2\n ;;\n --dirs)\n shift 2\n ;;\n *)\n src=\"$1\"\n shift\n ;;\n esac\ndone\nprintf 'compiled:%s\\n' \"$(basename \"$src\")\" >\"$out\"\nSH\nchmod +x \"$install_dir/nwn_script_comp\"\nprintf 'installed: %s\\n' \"$install_dir\"\n") - if err := os.Chmod(filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), 0o755); err != nil { - t.Fatalf("chmod fetch-hak-manifest.sh: %v", err) - } - if err := os.Chmod(filepath.Join(root, "scripts", "install-script-compiler.sh"), 0o755); err != nil { - t.Fatalf("chmod install-script-compiler.sh: %v", err) - } - t.Setenv("SOW_NWN_SCRIPT_COMPILER", "") - t.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "") - - var stdout bytes.Buffer - var stderr bytes.Buffer - ctx := context{ - stdout: &stdout, - stderr: &stderr, - cwd: root, - args: []string{"build-module"}, - } - - if err := runBuildModule(ctx); err != nil { - t.Fatalf("runBuildModule failed: %v\nstdout:\n%s\nstderr:\n%s", err, stdout.String(), stderr.String()) - } - - compiledScript := filepath.Join(root, ".cache", "ncs", "use_thing.ncs") - if _, err := os.Stat(compiledScript); err != nil { - t.Fatalf("expected installed compiler to compile script: %v", err) - } - if !strings.Contains(stdout.String(), "script compilation: installed") { - t.Fatalf("expected install summary in output, got %q", stdout.String()) - } - if strings.Contains(stdout.String(), "[build-module]") { - t.Fatalf("did not expect raw build-module progress in normal output, got %q", stdout.String()) - } -} - func TestRunMusicScanUsesConfiguredDatasetsWithoutWriting(t *testing.T) { root := t.TempDir() mkdirAll(t, filepath.Join(root, "assets", "audio", "westgate")) @@ -736,11 +573,3 @@ func setFileTime(t *testing.T, path string, modTime time.Time) { } } -func mustReadFile(t *testing.T, path string) string { - t.Helper() - raw, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } - return string(raw) -} diff --git a/internal/pipeline/build.go b/internal/pipeline/build.go index 54e98a7..a965427 100644 --- a/internal/pipeline/build.go +++ b/internal/pipeline/build.go @@ -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) diff --git a/internal/pipeline/compare.go b/internal/pipeline/compare.go index fb0ef3c..6bb3eee 100644 --- a/internal/pipeline/compare.go +++ b/internal/pipeline/compare.go @@ -164,23 +164,6 @@ func expectedResources(p *project.Project) (map[string]resourceExpectation, erro } } - compiledScripts, err := compileReferencedScripts(p) - if err != nil { - return nil, err - } - for _, resource := range compiledScripts { - extension, ok := erf.ExtensionForResourceType(resource.Type) - if !ok { - return nil, fmt.Errorf("unsupported compiled resource type for %s", resource.Name) - } - key := resourceKey(resource.Name, extension) - out[key] = resourceExpectation{ - Key: key, - Kind: "compiled-script", - Bytes: resource.Data, - } - } - for _, rel := range p.Inventory.AssetFiles { abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel)) data, err := os.ReadFile(abs) diff --git a/internal/pipeline/extract.go b/internal/pipeline/extract.go index 6f47c28..d5ccb40 100644 --- a/internal/pipeline/extract.go +++ b/internal/pipeline/extract.go @@ -289,7 +289,7 @@ func extractedFile(p *project.Project, resource erf.Resource, extension string) switch extension { case "nss": - target, err := extractionTarget(p, "paths.source", effective.Paths.Source, p.SourceDir(), filepath.FromSlash(effective.Scripts.SourceDir), resref+".nss") + target, err := extractionTarget(p, "paths.source", effective.Paths.Source, p.SourceDir(), "scripts", resref+".nss") if err != nil { return "", nil, err } diff --git a/internal/pipeline/pipeline_test.go b/internal/pipeline/pipeline_test.go index 0822661..03a0227 100644 --- a/internal/pipeline/pipeline_test.go +++ b/internal/pipeline/pipeline_test.go @@ -1028,205 +1028,6 @@ func TestBuildSplitsConfiguredHAKs(t *testing.T) { } } -func TestBuildModuleCompilesReferencedScripts(t *testing.T) { - root := t.TempDir() - mustMkdir(t, filepath.Join(root, "src", "module")) - mustMkdir(t, filepath.Join(root, "src", "placeables")) - mustMkdir(t, filepath.Join(root, "src", "scripts")) - mustMkdir(t, filepath.Join(root, ".nwn-assets")) - mustMkdir(t, filepath.Join(root, "build")) - - mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{ - "module": { - "name": "Test Module", - "resref": "testmod" - }, - "paths": { - "source": "src", - "assets": ".nwn-assets", - "build": "build" - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{ - "file_type": "IFO ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - { - "label": "Mod_Name", - "type": "CExoString", - "value": "Test Module" - } - ] - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{ - "file_type": "UTP ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - { - "label": "OnUsed", - "type": "ResRef", - "value": "use_thing" - } - ] - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), `void main() {}`) - - compiler := filepath.Join(root, "fake-compiler.sh") - mustWriteFile(t, compiler, `#!/bin/sh -set -eu -if [ "${1:-}" = "--help" ]; then - exit 0 -fi -out="" -src="" -while [ "$#" -gt 0 ]; do - case "$1" in - -o) - out="$2" - shift 2 - ;; - --dirs) - shift 2 - ;; - *) - src="$1" - shift - ;; - esac -done -[ -n "$out" ] -[ -n "$src" ] -printf 'compiled:%s\nNWN_ROOT=%s\nNWN_HOME=%s\nNWN_USER_DIRECTORY=%s\n' "$(basename "$src")" "${NWN_ROOT-}" "${NWN_HOME-}" "${NWN_USER_DIRECTORY-}" > "$out" -`) - if err := os.Chmod(compiler, 0o755); err != nil { - t.Fatalf("chmod fake compiler: %v", err) - } - t.Setenv("SOW_NWN_SCRIPT_COMPILER", compiler) - installRoot := filepath.Join(root, "nwn-install") - mustMkdir(t, filepath.Join(installRoot, "data")) - t.Setenv("SOW_NWN_ROOT", installRoot) - userDir := filepath.Join(root, "nwn-user") - t.Setenv("SOW_NWN_USER_DIRECTORY", userDir) - - p, err := project.Load(root) - if err != nil { - t.Fatalf("load project: %v", err) - } - if err := p.ValidateLayout(); err != nil { - t.Fatalf("validate layout: %v", err) - } - if err := p.Scan(); err != nil { - t.Fatalf("scan: %v", err) - } - - result, err := BuildModule(p) - if err != nil { - t.Fatalf("build module: %v", err) - } - if result.Resources != 4 { - t.Fatalf("expected 4 resources, got %d", result.Resources) - } - - fh, err := os.Open(result.ModulePath) - if err != nil { - t.Fatalf("open built module: %v", err) - } - defer fh.Close() - archive, err := erf.Read(fh) - if err != nil { - t.Fatalf("read built module: %v", err) - } - - foundSource := false - foundCompiled := false - for _, resource := range archive.Resources { - if resource.Name != "use_thing" { - continue - } - if extension, ok := erf.ExtensionForResourceType(resource.Type); ok { - switch extension { - case "nss": - foundSource = true - case "ncs": - foundCompiled = true - if !strings.Contains(string(resource.Data), "compiled:use_thing.nss") { - t.Fatalf("unexpected compiled payload: %q", string(resource.Data)) - } - if !strings.Contains(string(resource.Data), "NWN_ROOT="+installRoot) { - t.Fatalf("compiled payload missing NWN_ROOT: %q", string(resource.Data)) - } - if !strings.Contains(string(resource.Data), "NWN_HOME="+userDir) { - t.Fatalf("compiled payload missing NWN_HOME: %q", string(resource.Data)) - } - if !strings.Contains(string(resource.Data), "NWN_USER_DIRECTORY="+userDir) { - t.Fatalf("compiled payload missing NWN_USER_DIRECTORY: %q", string(resource.Data)) - } - } - } - } - - if !foundSource { - t.Fatalf("expected source script resource in archive") - } - if !foundCompiled { - t.Fatalf("expected compiled script resource in archive") - } - if _, err := os.Stat(userDir); err != nil { - t.Fatalf("expected build to prepare NWN user directory: %v", err) - } - if _, err := os.Stat(filepath.Join(root, ".cache", "ncs", "use_thing.ncs")); err != nil { - t.Fatalf("expected compiled script in .cache/ncs: %v", err) - } - if _, err := os.Stat(filepath.Join(root, ".nwn-assets", "ncs", "use_thing.ncs")); !os.IsNotExist(err) { - t.Fatalf("expected no compiled script in .nwn-assets/ncs, got err=%v", err) - } - - compareResult, err := Compare(p) - if err != nil { - t.Fatalf("compare: %v", err) - } - if compareResult.Checked != 4 { - t.Fatalf("expected 4 compared resources, got %d", compareResult.Checked) - } -} - -func TestParseSteamLibraryFolders(t *testing.T) { - root := t.TempDir() - vdf := filepath.Join(root, "libraryfolders.vdf") - mustWriteFile(t, vdf, `"libraryfolders" -{ - "0" - { - "path" "/mnt/games/Steam" - } - "1" "D:\\SteamLibrary" -} -`) - - got := parseSteamLibraryFolders(vdf) - if len(got) != 2 { - t.Fatalf("expected 2 library roots, got %d (%v)", len(got), got) - } - if got[0] != filepath.Clean("/mnt/games/Steam") { - t.Fatalf("unexpected first library root: %q", got[0]) - } - if got[1] != filepath.Clean(`D:\SteamLibrary`) { - t.Fatalf("unexpected second library root: %q", got[1]) - } -} - func TestBuildBuildsTopPackageAndInjectsHAKList(t *testing.T) { root := t.TempDir() mustMkdir(t, filepath.Join(root, "src", "module")) @@ -1344,118 +1145,6 @@ func TestBuildBuildsTopPackageAndInjectsHAKList(t *testing.T) { } } -func TestBuildModuleSkipsReferencedScriptCompilationWhenRequested(t *testing.T) { - root := t.TempDir() - mustMkdir(t, filepath.Join(root, "src", "module")) - mustMkdir(t, filepath.Join(root, "src", "placeables")) - mustMkdir(t, filepath.Join(root, "src", "scripts")) - mustMkdir(t, filepath.Join(root, ".nwn-assets")) - mustMkdir(t, filepath.Join(root, "build")) - - mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{ - "module": { - "name": "Test Module", - "resref": "testmod" - }, - "paths": { - "source": "src", - "assets": ".nwn-assets", - "build": "build" - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{ - "file_type": "IFO ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - { - "label": "Mod_Name", - "type": "CExoString", - "value": "Test Module" - } - ] - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{ - "file_type": "UTP ", - "file_version": "V3.2", - "root": { - "struct_type": 0, - "fields": [ - { - "label": "OnUsed", - "type": "ResRef", - "value": "use_thing" - } - ] - } -} -`) - - mustWriteFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), `void main() {}`) - t.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "1") - - p, err := project.Load(root) - if err != nil { - t.Fatalf("load project: %v", err) - } - if err := p.ValidateLayout(); err != nil { - t.Fatalf("validate layout: %v", err) - } - if err := p.Scan(); err != nil { - t.Fatalf("scan: %v", err) - } - - result, err := BuildModule(p) - if err != nil { - t.Fatalf("build module: %v", err) - } - if result.Resources != 3 { - t.Fatalf("expected 3 resources without compiled script, got %d", result.Resources) - } - - fh, err := os.Open(result.ModulePath) - if err != nil { - t.Fatalf("open built module: %v", err) - } - defer fh.Close() - archive, err := erf.Read(fh) - if err != nil { - t.Fatalf("read built module: %v", err) - } - - foundSource := false - foundCompiled := false - for _, resource := range archive.Resources { - if resource.Name != "use_thing" { - continue - } - if extension, ok := erf.ExtensionForResourceType(resource.Type); ok { - switch extension { - case "nss": - foundSource = true - case "ncs": - foundCompiled = true - } - } - } - - if !foundSource { - t.Fatalf("expected source script resource in archive") - } - if foundCompiled { - t.Fatalf("did not expect compiled script resource in archive") - } - if _, err := os.Stat(filepath.Join(root, ".cache", "ncs", "use_thing.ncs")); !os.IsNotExist(err) { - t.Fatalf("expected no compiled script cache file, got err=%v", err) - } -} - func TestBuildModuleDoesNotCompileTopData(t *testing.T) { root := t.TempDir() mustMkdir(t, filepath.Join(root, "src", "module")) diff --git a/internal/pipeline/script_compiler_env.go b/internal/pipeline/script_compiler_env.go deleted file mode 100644 index 994b3d9..0000000 --- a/internal/pipeline/script_compiler_env.go +++ /dev/null @@ -1,287 +0,0 @@ -package pipeline - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strings" - - "gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project" -) - -func resolveScriptCompilerEnvironment(p *project.Project) ([]string, error) { - env := os.Environ() - - userDir, err := resolveNWNUserDirectory(p.ScriptCompilerNWNUserDirectoryEnvKeys()) - if err != nil { - return nil, err - } - env = upsertEnv(env, "NWN_HOME", userDir) - env = upsertEnv(env, "NWN_USER_DIRECTORY", userDir) - - root, err := resolveNWNRoot(p.ScriptCompilerNWNRootEnvKeys()) - if err != nil { - return nil, err - } - if root != "" { - env = upsertEnv(env, "NWN_ROOT", root) - } - - return env, nil -} - -func resolveNWNUserDirectory(envKeys []string) (string, error) { - for _, key := range envKeys { - if value := strings.TrimSpace(os.Getenv(key)); value != "" { - path := filepath.Clean(expandUserPath(value)) - if err := os.MkdirAll(path, 0o755); err != nil { - return "", fmt.Errorf("prepare NWN user directory %s from %s: %w", path, key, err) - } - return path, nil - } - } - - path, err := defaultNWNUserDirectory() - if err != nil { - return "", err - } - if err := os.MkdirAll(path, 0o755); err != nil { - return "", fmt.Errorf("prepare default NWN user directory %s: %w", path, err) - } - return path, nil -} - -func defaultNWNUserDirectory() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("resolve user home directory: %w", err) - } - - switch runtime.GOOS { - case "windows", "darwin": - return filepath.Join(home, "Documents", "Neverwinter Nights"), nil - default: - base := strings.TrimSpace(os.Getenv("XDG_DATA_HOME")) - if base == "" { - base = filepath.Join(home, ".local", "share") - } - return filepath.Join(base, "Neverwinter Nights"), nil - } -} - -func resolveNWNRoot(envKeys []string) (string, error) { - for _, key := range envKeys { - if value := strings.TrimSpace(os.Getenv(key)); value != "" { - path := filepath.Clean(expandUserPath(value)) - if !looksLikeNWNInstall(path) { - return "", fmt.Errorf("%s does not point to a Neverwinter Nights install root: %s", key, path) - } - return path, nil - } - } - - for _, root := range candidateNWNInstallRoots() { - if looksLikeNWNInstall(root) { - return root, nil - } - } - - return "", nil -} - -func candidateNWNInstallRoots() []string { - candidates := make([]string, 0, 8) - for _, library := range steamLibraryRoots() { - candidates = append(candidates, filepath.Join(library, "steamapps", "common", "Neverwinter Nights")) - } - - switch runtime.GOOS { - case "windows": - for _, base := range []string{ - strings.TrimSpace(os.Getenv("PROGRAMFILES(X86)")), - strings.TrimSpace(os.Getenv("PROGRAMFILES")), - } { - if base == "" { - continue - } - candidates = append(candidates, filepath.Join(base, "Steam", "steamapps", "common", "Neverwinter Nights")) - } - case "darwin": - if home, err := os.UserHomeDir(); err == nil { - candidates = append(candidates, filepath.Join(home, "Library", "Application Support", "Steam", "steamapps", "common", "Neverwinter Nights")) - } - default: - if home, err := os.UserHomeDir(); err == nil { - candidates = append(candidates, - filepath.Join(home, ".local", "share", "Steam", "steamapps", "common", "Neverwinter Nights"), - filepath.Join(home, ".steam", "steam", "steamapps", "common", "Neverwinter Nights"), - filepath.Join(home, ".var", "app", "com.valvesoftware.Steam", ".local", "share", "Steam", "steamapps", "common", "Neverwinter Nights"), - ) - } - } - - return dedupePaths(candidates) -} - -func steamLibraryRoots() []string { - steamRoots := defaultSteamRoots() - roots := make([]string, 0, len(steamRoots)+4) - roots = append(roots, steamRoots...) - for _, steamRoot := range steamRoots { - roots = append(roots, parseSteamLibraryFolders(filepath.Join(steamRoot, "steamapps", "libraryfolders.vdf"))...) - } - return dedupePaths(roots) -} - -func defaultSteamRoots() []string { - roots := make([]string, 0, 6) - if runtime.GOOS == "windows" { - roots = append(roots, detectWindowsSteamRoot()) - roots = append(roots, - filepath.Join(strings.TrimSpace(os.Getenv("PROGRAMFILES(X86)")), "Steam"), - filepath.Join(strings.TrimSpace(os.Getenv("PROGRAMFILES")), "Steam"), - ) - return dedupePaths(filterNonEmpty(roots)) - } - - home, err := os.UserHomeDir() - if err != nil { - return nil - } - - switch runtime.GOOS { - case "darwin": - roots = append(roots, filepath.Join(home, "Library", "Application Support", "Steam")) - default: - roots = append(roots, - filepath.Join(home, ".local", "share", "Steam"), - filepath.Join(home, ".steam", "steam"), - filepath.Join(home, ".var", "app", "com.valvesoftware.Steam", ".local", "share", "Steam"), - ) - } - return dedupePaths(filterNonEmpty(roots)) -} - -func detectWindowsSteamRoot() string { - for _, args := range [][]string{ - {"query", `HKCU\Software\Valve\Steam`, "/v", "SteamPath"}, - {"query", `HKLM\SOFTWARE\WOW6432Node\Valve\Steam`, "/v", "InstallPath"}, - {"query", `HKLM\SOFTWARE\Valve\Steam`, "/v", "InstallPath"}, - } { - out, err := exec.Command("reg", args...).CombinedOutput() - if err != nil { - continue - } - if path := parseWindowsRegistryPath(string(out)); path != "" { - return filepath.Clean(path) - } - } - return "" -} - -func parseWindowsRegistryPath(raw string) string { - index := strings.Index(raw, "REG_SZ") - if index == -1 { - return "" - } - return strings.TrimSpace(raw[index+len("REG_SZ"):]) -} - -func parseSteamLibraryFolders(path string) []string { - raw, err := os.ReadFile(path) - if err != nil { - return nil - } - - pathPattern := regexp.MustCompile(`"path"\s*"([^"]+)"`) - legacyPattern := regexp.MustCompile(`^\s*"\d+"\s*"([^"]+)"\s*$`) - - roots := make([]string, 0, 8) - scanner := bufio.NewScanner(strings.NewReader(string(raw))) - for scanner.Scan() { - line := scanner.Text() - if match := pathPattern.FindStringSubmatch(line); len(match) == 2 { - roots = append(roots, normalizeSteamPath(match[1])) - continue - } - if match := legacyPattern.FindStringSubmatch(line); len(match) == 2 { - roots = append(roots, normalizeSteamPath(match[1])) - } - } - - return dedupePaths(filterNonEmpty(roots)) -} - -func normalizeSteamPath(value string) string { - value = strings.TrimSpace(value) - value = strings.ReplaceAll(value, `\\`, `\`) - return filepath.Clean(expandUserPath(value)) -} - -func looksLikeNWNInstall(path string) bool { - if strings.TrimSpace(path) == "" { - return false - } - info, err := os.Stat(filepath.Join(path, "data")) - return err == nil && info.IsDir() -} - -func expandUserPath(path string) string { - if path == "" || path[0] != '~' { - return path - } - - home, err := os.UserHomeDir() - if err != nil { - return path - } - if path == "~" { - return home - } - if path[1] == '/' || path[1] == '\\' { - return filepath.Join(home, path[2:]) - } - return path -} - -func upsertEnv(env []string, key, value string) []string { - prefix := key + "=" - for i, entry := range env { - if strings.HasPrefix(entry, prefix) { - env[i] = prefix + value - return env - } - } - return append(env, prefix+value) -} - -func dedupePaths(paths []string) []string { - seen := make(map[string]struct{}, len(paths)) - out := make([]string, 0, len(paths)) - for _, path := range paths { - if strings.TrimSpace(path) == "" { - continue - } - clean := filepath.Clean(path) - if _, ok := seen[clean]; ok { - continue - } - seen[clean] = struct{}{} - out = append(out, clean) - } - return out -} - -func filterNonEmpty(values []string) []string { - out := make([]string, 0, len(values)) - for _, value := range values { - if strings.TrimSpace(value) != "" { - out = append(out, value) - } - } - return out -} diff --git a/internal/project/effective.go b/internal/project/effective.go index 58c25b2..bc43ecb 100644 --- a/internal/project/effective.go +++ b/internal/project/effective.go @@ -21,9 +21,6 @@ const ( DefaultHAKArchiveTemplate = "{hak.name}.hak" DefaultSourceJSONPattern = "{resref}.{extension}.json" DefaultValidationProfile = "nwn_module" - DefaultScriptsSourceDir = "scripts" - DefaultScriptsCache = "{paths.cache}/ncs" - DefaultScriptCompilerEnvPath = "SOW_NWN_SCRIPT_COMPILER" DefaultMusicStageRoot = "{paths.cache}/music" DefaultCreditsRoot = "{paths.cache}/credits" DefaultCreditsOverlayFile = "CREDITS.md" @@ -88,7 +85,6 @@ type EffectiveConfig struct { Generated GeneratedConfig `json:"generated_assets" yaml:"generated_assets"` Inventory InventoryConfig `json:"inventory" yaml:"inventory"` Validation ValidationConfig `json:"validation" yaml:"validation"` - Scripts EffectiveScriptsConfig `json:"scripts" yaml:"scripts"` Music EffectiveMusicConfig `json:"music" yaml:"music"` TopData EffectiveTopDataConfig `json:"topdata" yaml:"topdata"` Extract ExtractConfig `json:"extract" yaml:"extract"` @@ -112,24 +108,6 @@ type EffectiveOutputConfig struct { HAKArchive string `json:"hak_archive" yaml:"hak_archive"` } -type EffectiveScriptsConfig struct { - SourceDir string `json:"source_dir" yaml:"source_dir"` - Cache string `json:"cache" yaml:"cache"` - Compiler EffectiveScriptCompilerConfig `json:"compiler" yaml:"compiler"` -} - -type EffectiveScriptCompilerConfig struct { - Path string `json:"path" yaml:"path"` - Search []string `json:"search" yaml:"search"` - Env EffectiveScriptCompilerEnvConfig `json:"env" yaml:"env"` -} - -type EffectiveScriptCompilerEnvConfig struct { - Path string `json:"path" yaml:"path"` - NWNRoot []string `json:"nwn_root" yaml:"nwn_root"` - NWNUserDirectory []string `json:"nwn_user_directory" yaml:"nwn_user_directory"` -} - type EffectiveMusicConfig struct { Prefixes map[string]string `json:"prefixes" yaml:"prefixes"` Tools MusicToolsConfig `json:"tools" yaml:"tools"` @@ -225,19 +203,6 @@ func (p *Project) EffectiveConfig() EffectiveConfig { BuiltinScriptPrefixes: normalizeLowerStringSlice(defaultStringSlice(p.Config.Validation.BuiltinScriptPrefixes, DefaultBuiltinScriptPrefixes())), RequiredFields: defaultRequiredFields(p.Config.Validation.RequiredFields), } - scripts := EffectiveScriptsConfig{ - SourceDir: defaultString(p.Config.Scripts.SourceDir, DefaultScriptsSourceDir), - Cache: expandPathTemplate(defaultString(p.Config.Scripts.Cache, DefaultScriptsCache), paths), - Compiler: EffectiveScriptCompilerConfig{ - Path: strings.TrimSpace(p.Config.Scripts.Compiler.Path), - Search: expandPathTemplates(defaultStringSlice(p.Config.Scripts.Compiler.Search, defaultScriptCompilerSearch()), paths), - Env: EffectiveScriptCompilerEnvConfig{ - Path: defaultString(p.Config.Scripts.Compiler.Env.Path, DefaultScriptCompilerEnvPath), - NWNRoot: defaultStringSlice(p.Config.Scripts.Compiler.Env.NWNRoot, []string{"SOW_NWN_ROOT", "NWN_ROOT"}), - NWNUserDirectory: defaultStringSlice(p.Config.Scripts.Compiler.Env.NWNUserDirectory, []string{"SOW_NWN_USER_DIRECTORY", "NWN_HOME", "NWN_USER_DIRECTORY"}), - }, - }, - } topBuild := expandPathTemplate(defaultString(p.Config.TopData.Build, DefaultTopDataBuild), paths) top := EffectiveTopDataConfig{ Source: strings.TrimSpace(p.Config.TopData.Source), @@ -394,7 +359,6 @@ func (p *Project) EffectiveConfig() EffectiveConfig { Generated: generated, Inventory: inventory, Validation: validation, - Scripts: scripts, Music: EffectiveMusicConfig{ Prefixes: musicPrefixes, Tools: p.Config.Music.Tools, @@ -553,10 +517,6 @@ func markMissingDefaults(provenance ConfigProvenance) { "validation.profile": DefaultValidationProfile, "validation.builtin_script_prefixes": "NWN built-in script prefixes", "validation.required_fields": "NWN required GFF fields", - "scripts.source_dir": DefaultScriptsSourceDir, - "scripts.cache": DefaultScriptsCache, - "scripts.compiler.env.path": DefaultScriptCompilerEnvPath, - "scripts.compiler.search": "repo tools and PATH:nwn_script_comp", "music.stage_root": DefaultMusicStageRoot, "music.credits_root": DefaultCreditsRoot, "music.credits_overlay": DefaultCreditsOverlayFile, @@ -719,17 +679,6 @@ func expandPathTemplates(templates []string, paths EffectivePathConfig) []string return out } -func defaultScriptCompilerSearch() []string { - return []string{ - "{paths.tools}/script-compiler/nwn_script_comp", - "{paths.tools}/script-compiler/nwn_script_comp.exe", - "{paths.tools}/nwn_script_comp", - "{paths.tools}/nwn_script_comp.exe", - "PATH:nwn_script_comp", - "PATH:nwn_script_comp.exe", - } -} - func (p *Project) ActiveOverrides() []ConfigOverride { return activeOverrides(p.EffectiveConfig()) } @@ -737,7 +686,6 @@ func (p *Project) ActiveOverrides() []ConfigOverride { func activeOverrides(effective EffectiveConfig) []ConfigOverride { envs := map[string]string{ "build.keep_existing_haks": effectiveBuildKeepExistingEnv, - "scripts.compiler.path": effective.Scripts.Compiler.Env.Path, "autogen.cache.refresh": effective.Autogen.Cache.RefreshEnv, "autogen.cache.parts_manifest_refresh": effective.Autogen.Cache.PartsManifestRefreshEnv, "autogen.release_source.server_url": effective.Autogen.ReleaseSource.ServerURLEnv, @@ -749,12 +697,6 @@ func activeOverrides(effective EffectiveConfig) []ConfigOverride { "wiki.categories": "NODEBB_WIKI_CATEGORIES", "release.version": "GITHUB_REF_NAME", } - for _, key := range effective.Scripts.Compiler.Env.NWNRoot { - envs["scripts.compiler.env.nwn_root."+key] = key - } - for _, key := range effective.Scripts.Compiler.Env.NWNUserDirectory { - envs["scripts.compiler.env.nwn_user_directory."+key] = key - } keys := make([]string, 0, len(envs)) for key := range envs { keys = append(keys, key) diff --git a/internal/project/project.go b/internal/project/project.go index 0560c02..6f719f8 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -88,7 +88,6 @@ type Config struct { HAKs []HAKConfig `json:"haks" yaml:"haks"` Inventory InventoryConfig `json:"inventory" yaml:"inventory"` Validation ValidationConfig `json:"validation" yaml:"validation"` - Scripts ScriptsConfig `json:"scripts" yaml:"scripts"` Music MusicConfig `json:"music" yaml:"music"` TopData TopDataConfig `json:"topdata" yaml:"topdata"` Extract ExtractConfig `json:"extract" yaml:"extract"` @@ -145,24 +144,6 @@ type ValidationConfig struct { RequiredFields map[string][]string `json:"required_fields" yaml:"required_fields"` } -type ScriptsConfig struct { - SourceDir string `json:"source_dir" yaml:"source_dir"` - Cache string `json:"cache" yaml:"cache"` - Compiler ScriptCompilerConfig `json:"compiler" yaml:"compiler"` -} - -type ScriptCompilerConfig struct { - Path string `json:"path" yaml:"path"` - Search []string `json:"search" yaml:"search"` - Env ScriptCompilerEnvConfig `json:"env" yaml:"env"` -} - -type ScriptCompilerEnvConfig struct { - Path string `json:"path" yaml:"path"` - NWNRoot []string `json:"nwn_root" yaml:"nwn_root"` - NWNUserDirectory []string `json:"nwn_user_directory" yaml:"nwn_user_directory"` -} - type HAKConfig struct { Name string `json:"name" yaml:"name"` Priority int `json:"priority" yaml:"priority"` @@ -609,10 +590,6 @@ func (p *Project) ValidateLayout() error { "outputs.hak_archive": p.Config.Outputs.HAKArchive, "generated_assets.topdata_2da": "", "validation.profile": p.Config.Validation.Profile, - "scripts.source_dir": p.Config.Scripts.SourceDir, - "scripts.cache": p.Config.Scripts.Cache, - "scripts.compiler.path": p.Config.Scripts.Compiler.Path, - "scripts.compiler.env.path": p.Config.Scripts.Compiler.Env.Path, "topdata.source": p.Config.TopData.Source, "topdata.build": p.Config.TopData.Build, "topdata.reference_builder": p.Config.TopData.ReferenceBuilder, @@ -663,8 +640,6 @@ func (p *Project) ValidateLayout() error { failures = append(failures, validateTopDataRowGeneration(effective.TopData.RowGeneration)...) failures = append(failures, validateTopDataRowExtensions(effective.TopData.RowExtensions)...) failures = append(failures, validateTopDataClassFeatInjections(effective.TopData.ClassFeatInjections)...) - failures = append(failures, validateRelativePath("scripts.cache", effective.Scripts.Cache)...) - failures = append(failures, validateRelativePath("scripts.source_dir", effective.Scripts.SourceDir)...) failures = append(failures, validateRelativePath("topdata.compiled_2da_dir", effective.TopData.Compiled2DADir)...) failures = append(failures, validateRelativePath("topdata.compiled_tlk", effective.TopData.CompiledTLK)...) failures = append(failures, validateRelativePath("topdata.wiki.output_root", effective.TopData.Wiki.OutputRoot)...) @@ -1347,54 +1322,6 @@ func (p *Project) TopDataPackageTLKPath() string { return filepath.Join(p.BuildDir(), p.TopDataPackageTLKName()) } -func (p *Project) ScriptSourceDir() string { - return filepath.Join(p.SourceDir(), filepath.FromSlash(p.EffectiveConfig().Scripts.SourceDir)) -} - -func (p *Project) ScriptCacheDir() string { - return p.rootPath(p.EffectiveConfig().Scripts.Cache) -} - -func (p *Project) ScriptCompilerPath() string { - path := strings.TrimSpace(p.EffectiveConfig().Scripts.Compiler.Path) - if path == "" { - return "" - } - if filepath.IsAbs(path) { - return path - } - return filepath.Join(p.Root, filepath.FromSlash(path)) -} - -func (p *Project) ScriptCompilerSearchPaths() []string { - search := p.EffectiveConfig().Scripts.Compiler.Search - out := make([]string, 0, len(search)) - for _, candidate := range search { - candidate = strings.TrimSpace(candidate) - if candidate == "" { - continue - } - if strings.HasPrefix(candidate, "PATH:") || filepath.IsAbs(candidate) { - out = append(out, candidate) - continue - } - out = append(out, filepath.Join(p.Root, filepath.FromSlash(candidate))) - } - return out -} - -func (p *Project) ScriptCompilerPathEnv() string { - return p.EffectiveConfig().Scripts.Compiler.Env.Path -} - -func (p *Project) ScriptCompilerNWNRootEnvKeys() []string { - return slices.Clone(p.EffectiveConfig().Scripts.Compiler.Env.NWNRoot) -} - -func (p *Project) ScriptCompilerNWNUserDirectoryEnvKeys() []string { - return slices.Clone(p.EffectiveConfig().Scripts.Compiler.Env.NWNUserDirectory) -} - func (p *Project) MusicStageDir() string { return p.rootPath(p.EffectiveConfig().Music.StageRoot) } diff --git a/internal/project/project_test.go b/internal/project/project_test.go index 6da4cfa..833c7cc 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -462,8 +462,6 @@ outputs: module_archive: modules/{module.resref}.mod hak_manifest: manifests/haks.json hak_archive: haks/{hak.name}.hak -scripts: - cache: "{paths.cache}/compiled" topdata: source: topdata build: "{paths.cache}" @@ -500,9 +498,6 @@ autogen: if got, want := proj.HAKArchivePath("core_01"), filepath.Join(root, "output", "haks", "core_01.hak"); got != want { t.Fatalf("expected HAK archive path %s, got %s", want, got) } - if got, want := proj.ScriptCacheDir(), filepath.Join(root, "cache", "compiled"); got != want { - t.Fatalf("expected script cache path %s, got %s", want, got) - } if got, want := proj.TopDataCompiled2DADir(), filepath.Join(root, "cache", "tables"); got != want { t.Fatalf("expected topdata 2da path %s, got %s", want, got) } @@ -529,7 +524,7 @@ autogen: } } -func TestEffectiveConfigHonorsConfiguredCompilerExtractWikiAndAutogenPolicy(t *testing.T) { +func TestEffectiveConfigHonorsConfiguredExtractWikiAndAutogenPolicy(t *testing.T) { root := t.TempDir() writeProjectFile(t, filepath.Join(root, ConfigFile), ` module: @@ -538,18 +533,6 @@ module: paths: source: src tools: custom-tools -scripts: - source_dir: module-scripts - compiler: - path: bin/compiler - search: - - "{paths.tools}/compiler" - env: - path: TEST_COMPILER - nwn_root: - - TEST_NWN_ROOT - nwn_user_directory: - - TEST_NWN_USER extract: layout: nwn_canonical_json hak_discovery: configured_haks @@ -599,18 +582,6 @@ autogen: t.Fatalf("Load returned error: %v", err) } effective := proj.EffectiveConfig() - if got, want := proj.ScriptSourceDir(), filepath.Join(root, "src", "module-scripts"); got != want { - t.Fatalf("expected script source dir %s, got %s", want, got) - } - if got, want := proj.ScriptCompilerPath(), filepath.Join(root, "bin", "compiler"); got != want { - t.Fatalf("expected compiler path %s, got %s", want, got) - } - if got, want := effective.Scripts.Compiler.Search[0], "custom-tools/compiler"; got != want { - t.Fatalf("expected compiler search %q, got %q", want, got) - } - if got, want := effective.Scripts.Compiler.Env.Path, "TEST_COMPILER"; got != want { - t.Fatalf("expected compiler env %q, got %q", want, got) - } if got, want := effective.Extract.HAKDiscovery, "configured_haks"; got != want { t.Fatalf("expected extract hak discovery %q, got %q", want, got) }