246 lines
6.1 KiB
Go
246 lines
6.1 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
|
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/gff"
|
|
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
|
)
|
|
|
|
type CompareResult struct {
|
|
ModulePath string
|
|
HAKPaths []string
|
|
Checked int
|
|
}
|
|
|
|
type resourceExpectation struct {
|
|
Key string
|
|
Kind string
|
|
Bytes []byte
|
|
}
|
|
|
|
func Compare(p *project.Project) (CompareResult, error) {
|
|
modulePath := filepath.Join(p.BuildDir(), p.Config.Module.ResRef+".mod")
|
|
moduleArchive, err := readArchive(modulePath)
|
|
if err != nil {
|
|
return CompareResult{}, err
|
|
}
|
|
|
|
var hakPaths []string
|
|
if len(p.Inventory.AssetFiles) > 0 {
|
|
var err error
|
|
hakPaths, err = manifestHAKPaths(p.BuildDir())
|
|
if err != nil {
|
|
return CompareResult{}, err
|
|
}
|
|
}
|
|
|
|
expected, err := expectedResources(p)
|
|
if err != nil {
|
|
return CompareResult{}, err
|
|
}
|
|
|
|
actual := archiveIndex(moduleArchive)
|
|
for _, hakPath := range hakPaths {
|
|
hakArchive, err := readArchive(hakPath)
|
|
if err != nil {
|
|
return CompareResult{}, err
|
|
}
|
|
for key, value := range archiveIndex(hakArchive) {
|
|
actual[key] = value
|
|
}
|
|
}
|
|
|
|
var diagnostics []error
|
|
checked := 0
|
|
|
|
expectedKeys := make([]string, 0, len(expected))
|
|
for key := range expected {
|
|
expectedKeys = append(expectedKeys, key)
|
|
}
|
|
slices.Sort(expectedKeys)
|
|
|
|
for _, key := range expectedKeys {
|
|
want := expected[key]
|
|
got, ok := actual[key]
|
|
if !ok {
|
|
diagnostics = append(diagnostics, fmt.Errorf("missing resource in built archives: %s", key))
|
|
continue
|
|
}
|
|
if !bytes.Equal(want.Bytes, got.Data) {
|
|
diagnostics = append(diagnostics, fmt.Errorf("resource content mismatch: %s", key))
|
|
continue
|
|
}
|
|
checked++
|
|
delete(actual, key)
|
|
}
|
|
|
|
if len(actual) > 0 {
|
|
extraKeys := make([]string, 0, len(actual))
|
|
for key := range actual {
|
|
extraKeys = append(extraKeys, key)
|
|
}
|
|
slices.Sort(extraKeys)
|
|
for _, key := range extraKeys {
|
|
diagnostics = append(diagnostics, fmt.Errorf("unexpected extra resource in built archives: %s", key))
|
|
}
|
|
}
|
|
|
|
result := CompareResult{
|
|
ModulePath: modulePath,
|
|
HAKPaths: hakPaths,
|
|
Checked: checked,
|
|
}
|
|
if len(diagnostics) > 0 {
|
|
return result, errors.Join(diagnostics...)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func expectedResources(p *project.Project) (map[string]resourceExpectation, error) {
|
|
out := map[string]resourceExpectation{}
|
|
|
|
for _, rel := range p.Inventory.SourceFiles {
|
|
abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
|
|
name, extension, err := splitSourceName(abs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
raw, err := os.ReadFile(abs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read %s: %w", abs, err)
|
|
}
|
|
var document gff.Document
|
|
if err := json.Unmarshal(raw, &document); err != nil {
|
|
return nil, fmt.Errorf("parse gff json %s: %w", abs, err)
|
|
}
|
|
|
|
canonical, err := json.Marshal(document)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal canonical gff json %s: %w", abs, err)
|
|
}
|
|
|
|
out[resourceKey(name, extension)] = resourceExpectation{
|
|
Key: resourceKey(name, extension),
|
|
Kind: "gff-json",
|
|
Bytes: canonical,
|
|
}
|
|
}
|
|
|
|
for _, rel := range p.Inventory.ScriptFiles {
|
|
abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
|
|
data, err := os.ReadFile(abs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read %s: %w", abs, err)
|
|
}
|
|
name := strings.TrimSuffix(filepath.Base(abs), filepath.Ext(abs))
|
|
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(abs)), ".")
|
|
out[resourceKey(name, extension)] = resourceExpectation{
|
|
Key: resourceKey(name, extension),
|
|
Kind: "raw",
|
|
Bytes: data,
|
|
}
|
|
}
|
|
|
|
for _, rel := range p.Inventory.AssetFiles {
|
|
abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel))
|
|
data, err := os.ReadFile(abs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read %s: %w", abs, err)
|
|
}
|
|
name := strings.TrimSuffix(filepath.Base(abs), filepath.Ext(abs))
|
|
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(abs)), ".")
|
|
out[resourceKey(name, extension)] = resourceExpectation{
|
|
Key: resourceKey(name, extension),
|
|
Kind: "raw",
|
|
Bytes: data,
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func readArchive(path string) (erf.Archive, error) {
|
|
input, err := os.Open(path)
|
|
if err != nil {
|
|
return erf.Archive{}, fmt.Errorf("open archive %s: %w", path, err)
|
|
}
|
|
defer input.Close()
|
|
|
|
archive, err := erf.Read(input)
|
|
if err != nil {
|
|
return erf.Archive{}, fmt.Errorf("read archive %s: %w", path, err)
|
|
}
|
|
return archive, nil
|
|
}
|
|
|
|
func archiveIndex(archive erf.Archive) map[string]erf.Resource {
|
|
out := map[string]erf.Resource{}
|
|
for _, resource := range archive.Resources {
|
|
extension, ok := erf.ExtensionForResourceType(resource.Type)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
key := resourceKey(resource.Name, extension)
|
|
switch extension {
|
|
case "utc", "utd", "ute", "uti", "utm", "utp", "uts", "utt", "utw",
|
|
"are", "dlg", "fac", "gic", "git", "ifo", "itp", "jrl":
|
|
document, err := gff.Read(bytes.NewReader(resource.Data))
|
|
if err != nil {
|
|
out[key] = resource
|
|
continue
|
|
}
|
|
canonical, err := json.Marshal(document)
|
|
if err != nil {
|
|
out[key] = resource
|
|
continue
|
|
}
|
|
resource.Data = canonical
|
|
}
|
|
|
|
out[key] = resource
|
|
}
|
|
return out
|
|
}
|
|
|
|
func resourceKey(name, extension string) string {
|
|
return name + "." + strings.TrimPrefix(strings.ToLower(extension), ".")
|
|
}
|
|
|
|
func manifestHAKPaths(buildDir string) ([]string, error) {
|
|
manifestPath := filepath.Join(buildDir, "haks.json")
|
|
raw, err := os.ReadFile(manifestPath)
|
|
if err == nil {
|
|
var manifest BuildManifest
|
|
if err := json.Unmarshal(raw, &manifest); err != nil {
|
|
return nil, fmt.Errorf("parse hak manifest: %w", err)
|
|
}
|
|
paths := make([]string, 0, len(manifest.HAKs))
|
|
for _, hak := range manifest.HAKs {
|
|
paths = append(paths, filepath.Join(buildDir, hak.Name+".hak"))
|
|
}
|
|
return paths, nil
|
|
}
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("read hak manifest: %w", err)
|
|
}
|
|
|
|
legacy := filepath.Join(buildDir, "*.hak")
|
|
paths, err := filepath.Glob(legacy)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan hak archives: %w", err)
|
|
}
|
|
slices.Sort(paths)
|
|
return paths, nil
|
|
}
|