Update BuildAndPackage
This commit is contained in:
+1
-1
@@ -397,7 +397,7 @@ func runBuildTopPackage(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := topdata.BuildPackage(p, func(message string) {
|
||||
result, err := topdata.BuildAndPackage(p, func(message string) {
|
||||
fmt.Fprintf(ctx.stdout, "[build-top-package] %s\n", message)
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -94,8 +94,16 @@ func buildModule(p *project.Project, progress ProgressFunc) (BuildResult, error)
|
||||
var topPackage topdata.PackageResult
|
||||
var err error
|
||||
if p.HasTopData() {
|
||||
progressf(progress, "Building native top package...")
|
||||
topPackage, err = topdata.BuildPackage(p, func(message string) {
|
||||
progressf(progress, "Building native topdata...")
|
||||
nativeTopData, buildErr := topdata.BuildNative(p, func(message string) {
|
||||
progressf(progress, "Top package: "+message)
|
||||
})
|
||||
if buildErr != nil {
|
||||
return BuildResult{}, buildErr
|
||||
}
|
||||
|
||||
progressf(progress, "Packaging native top package...")
|
||||
topPackage, err = topdata.BuildPackageFromBuild(p, nativeTopData, func(message string) {
|
||||
progressf(progress, "Top package: "+message)
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -546,6 +546,74 @@ func TestBuildModuleBuildsTopPackageAndInjectsHAKList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildModuleCompilesTopDataOnlyOnce(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src", "module"))
|
||||
mustMkdir(t, filepath.Join(root, "assets"))
|
||||
mustMkdir(t, filepath.Join(root, "build"))
|
||||
mustMkdir(t, filepath.Join(root, "topdata", "data", "repadjust"))
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {
|
||||
"name": "Test Module",
|
||||
"resref": "testmod",
|
||||
"hak_order": ["sow_top"]
|
||||
},
|
||||
"paths": {
|
||||
"source": "src",
|
||||
"assets": "assets",
|
||||
"build": "build"
|
||||
},
|
||||
"topdata": {
|
||||
"source": "topdata",
|
||||
"build": "build/topdata"
|
||||
}
|
||||
}
|
||||
`)
|
||||
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"},
|
||||
{"label": "Mod_CustomTlk", "type": "CExoString", "value": "sow_tlk"}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
mustWriteFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||
mustWriteFile(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), `{
|
||||
"output": "repadjust.2da",
|
||||
"columns": ["Label"],
|
||||
"rows": [{"id": 0, "Label": "TEST_LABEL"}]
|
||||
}
|
||||
`)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var progress []string
|
||||
if _, err := BuildModuleWithProgress(p, func(message string) {
|
||||
progress = append(progress, message)
|
||||
}); err != nil {
|
||||
t.Fatalf("build module: %v", err)
|
||||
}
|
||||
|
||||
joined := strings.Join(progress, "\n")
|
||||
if got := strings.Count(joined, "Building native topdata datasets..."); got != 1 {
|
||||
t.Fatalf("expected exactly one native topdata compile pass, got %d\nprogress:\n%s", got, joined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareFailsWhenTopPackageIsStale(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src", "module"))
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package topdata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||
@@ -19,6 +22,17 @@ const (
|
||||
)
|
||||
|
||||
func BuildPackage(p *project.Project, progress func(string)) (PackageResult, error) {
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
}
|
||||
nativeResult, err := packagedBuildResult(p)
|
||||
if err != nil {
|
||||
return PackageResult{}, err
|
||||
}
|
||||
return packageBuiltTopData(p, nativeResult, progress)
|
||||
}
|
||||
|
||||
func BuildAndPackage(p *project.Project, progress func(string)) (PackageResult, error) {
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
}
|
||||
@@ -29,6 +43,13 @@ func BuildPackage(p *project.Project, progress func(string)) (PackageResult, err
|
||||
return packageBuiltTopData(p, nativeResult, progress)
|
||||
}
|
||||
|
||||
func BuildPackageFromBuild(p *project.Project, nativeResult BuildResult, progress func(string)) (PackageResult, error) {
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
}
|
||||
return packageBuiltTopData(p, nativeResult, progress)
|
||||
}
|
||||
|
||||
func packageBuiltTopData(p *project.Project, nativeResult BuildResult, progress func(string)) (PackageResult, error) {
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
@@ -207,3 +228,103 @@ func copyFile(dst, src string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func packagedBuildResult(p *project.Project) (BuildResult, error) {
|
||||
if !p.HasTopData() {
|
||||
return BuildResult{}, errors.New("topdata is not configured for this project")
|
||||
}
|
||||
|
||||
buildDir := p.TopDataBuildDir()
|
||||
output2DA := filepath.Join(buildDir, "2da")
|
||||
outputTLK := filepath.Join(buildDir, "tlk")
|
||||
if _, err := os.Stat(output2DA); err != nil {
|
||||
return BuildResult{}, fmt.Errorf("topdata build output missing: run build-topdata first")
|
||||
}
|
||||
if _, err := os.Stat(outputTLK); err != nil {
|
||||
return BuildResult{}, fmt.Errorf("topdata tlk output missing: run build-topdata first")
|
||||
}
|
||||
|
||||
files2DA, oldest2DA, err := countCompiledFiles(output2DA, ".2da")
|
||||
if err != nil {
|
||||
return BuildResult{}, err
|
||||
}
|
||||
filesTLK, oldestTLK, err := countCompiledFiles(outputTLK, ".tlk")
|
||||
if err != nil {
|
||||
return BuildResult{}, err
|
||||
}
|
||||
if files2DA == 0 {
|
||||
return BuildResult{}, fmt.Errorf("topdata build output missing: run build-topdata first")
|
||||
}
|
||||
if filesTLK != 1 {
|
||||
return BuildResult{}, fmt.Errorf("expected exactly 1 compiled tlk output, found %d", filesTLK)
|
||||
}
|
||||
|
||||
newestSource, newestPath, err := newestTopDataSource(p.TopDataSourceDir())
|
||||
if err != nil {
|
||||
return BuildResult{}, err
|
||||
}
|
||||
oldestBuild := oldest2DA
|
||||
if oldestBuild.IsZero() || (!oldestTLK.IsZero() && oldestTLK.Before(oldestBuild)) {
|
||||
oldestBuild = oldestTLK
|
||||
}
|
||||
if !newestSource.IsZero() && !oldestBuild.IsZero() && newestSource.After(oldestBuild) {
|
||||
return BuildResult{}, fmt.Errorf("topdata build output is older than source file %s; run build-topdata first", newestPath)
|
||||
}
|
||||
|
||||
return BuildResult{
|
||||
Mode: "native",
|
||||
Output2DADir: output2DA,
|
||||
OutputTLKDir: outputTLK,
|
||||
Files2DA: files2DA,
|
||||
FilesTLK: filesTLK,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func countCompiledFiles(dir, extension string) (int, time.Time, error) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return 0, time.Time{}, fmt.Errorf("read compiled output dir %s: %w", dir, err)
|
||||
}
|
||||
count := 0
|
||||
oldest := time.Time{}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), extension) {
|
||||
continue
|
||||
}
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return 0, time.Time{}, fmt.Errorf("stat compiled output %s: %w", filepath.Join(dir, entry.Name()), err)
|
||||
}
|
||||
count++
|
||||
if oldest.IsZero() || info.ModTime().Before(oldest) {
|
||||
oldest = info.ModTime()
|
||||
}
|
||||
}
|
||||
return count, oldest, nil
|
||||
}
|
||||
|
||||
func newestTopDataSource(sourceDir string) (time.Time, string, error) {
|
||||
newest := time.Time{}
|
||||
newestPath := ""
|
||||
err := filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newest.IsZero() || info.ModTime().After(newest) {
|
||||
newest = info.ModTime()
|
||||
newestPath = path
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return time.Time{}, "", fmt.Errorf("scan topdata sources: %w", err)
|
||||
}
|
||||
return newest, newestPath, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||
@@ -8062,7 +8063,7 @@ func TestBuildNativeWithSiblingAssetsRepo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPackageIncludesCompiled2DAAndTopAssets(t *testing.T) {
|
||||
func TestBuildAndPackageIncludesCompiled2DAAndTopAssets(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui"))
|
||||
@@ -8077,9 +8078,9 @@ func TestBuildPackageIncludesCompiled2DAAndTopAssets(t *testing.T) {
|
||||
proj := testProject(root)
|
||||
proj.Config.TopData.ReferenceBuilder = ""
|
||||
|
||||
result, err := BuildPackage(proj, nil)
|
||||
result, err := BuildAndPackage(proj, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildPackage failed: %v", err)
|
||||
t.Fatalf("BuildAndPackage failed: %v", err)
|
||||
}
|
||||
if result.OutputHAKPath != filepath.Join(root, "build", PackageHAKFileName) {
|
||||
t.Fatalf("unexpected hak path: %s", result.OutputHAKPath)
|
||||
@@ -8120,6 +8121,52 @@ func TestBuildPackageIncludesCompiled2DAAndTopAssets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPackageRequiresExistingCompiledOutput(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), `{
|
||||
"output": "repadjust.2da",
|
||||
"columns": ["Label"],
|
||||
"rows": [{"id": 0, "Label": "TEST_LABEL"}]
|
||||
}`+"\n")
|
||||
|
||||
proj := testProject(root)
|
||||
proj.Config.TopData.ReferenceBuilder = ""
|
||||
|
||||
if _, err := BuildPackage(proj, nil); err == nil || !strings.Contains(err.Error(), "run build-topdata first") {
|
||||
t.Fatalf("expected missing compiled output error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPackageFailsWhenCompiledOutputIsStale(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||
basePath := filepath.Join(root, "topdata", "data", "repadjust", "base.json")
|
||||
writeFile(t, basePath, `{
|
||||
"output": "repadjust.2da",
|
||||
"columns": ["Label"],
|
||||
"rows": [{"id": 0, "Label": "TEST_LABEL"}]
|
||||
}`+"\n")
|
||||
|
||||
proj := testProject(root)
|
||||
proj.Config.TopData.ReferenceBuilder = ""
|
||||
|
||||
if _, err := BuildNative(proj, nil); err != nil {
|
||||
t.Fatalf("BuildNative failed: %v", err)
|
||||
}
|
||||
|
||||
newTime := time.Now().Add(2 * time.Second)
|
||||
if err := os.Chtimes(basePath, newTime, newTime); err != nil {
|
||||
t.Fatalf("touch topdata source: %v", err)
|
||||
}
|
||||
|
||||
if _, err := BuildPackage(proj, nil); err == nil || !strings.Contains(err.Error(), "older than source file") {
|
||||
t.Fatalf("expected stale compiled output error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProjectErrorsOnTopPackageAssetCollision(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust"))
|
||||
|
||||
Reference in New Issue
Block a user