build concerns, build matrix

This commit is contained in:
2026-08-04 08:37:49 +02:00
parent f943357ebd
commit 77f269d9e0
36 changed files with 602 additions and 57 deletions
@@ -0,0 +1,5 @@
class Api::BuildsController < ApiController
def index
render json: BuildsService.new.index
end
end
@@ -0,0 +1,5 @@
class Api::SoftwareBuildsController < ApiController
def show
render json: BuildsService.new.show(params[:name])
end
end
+37
View File
@@ -0,0 +1,37 @@
class BuildsService
def index
platforms = UpdateService::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash|
service_class = "SoftwareUpdater::#{platform.camelize}Service".constantize
hash[platform] = {
label: service_class.label,
kinds: service_class.expected_kinds
}
end
all_kinds = ReleaseAsset::KINDS
{ platforms: platforms, allKinds: all_kinds }
end
def show(name)
software = Software.find_by!(name: name)
service_class = "SoftwareUpdater::#{software.platform.camelize}Service".constantize
expected = service_class.expected_kinds
releases = software.releases.includes(:release_assets).order(updated_at: :desc)
release_data = releases.each_with_object({}) do |release, hash|
actual = release.release_assets.map(&:kind)
hash[release.version] = {
actual: actual,
missing: expected - actual
}
end
{
platform: software.platform,
expected: expected,
releases: release_data
}
end
end
@@ -1,23 +0,0 @@
module SoftwareUpdater
module BinaryAttachment
# fájlnév slug → ReleaseAsset kind
TARGET_KINDS = {
"win-x86" => "win_x86",
"win-x64" => "win_x64",
"linux-x86" => "linux_x86",
"linux-x64" => "linux_x64",
"mac-x64" => "mac_x64",
"mac-arm64" => "mac_arm64",
"mac-universal" => "mac_universal"
}.freeze
private
def binary_asset_paths(versioned)
TARGET_KINDS.each_with_object({}) do |(slug, kind), assets|
filename = "#{versioned}-#{slug}.zip"
assets[kind] = full_path(filename) if File.file?(full_path(filename))
end
end
end
end
@@ -0,0 +1,22 @@
module SoftwareUpdater
module Builds
module BuildCartridge
extend ActiveSupport::Concern
included do
register_expected_kind "cartridge"
end
def cartridge_ext
raise NotImplementedError, "#{self.class} must implement #cartridge_ext"
end
private
def cartridge_asset_path(versioned)
path = full_path("#{versioned}#{cartridge_ext}")
{ "cartridge" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,24 @@
module SoftwareUpdater
module Builds
module BuildDocs
extend ActiveSupport::Concern
included do
register_expected_kind "docs"
register_prepare_step :prepare_docs
end
private
def prepare_docs(versioned)
docs_zip = "#{versioned}-docs.zip"
extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip))
end
def docs_asset_path(versioned)
docs_dir = "#{versioned}-docs"
{ "docs" => full_path(docs_dir) } if dir_exists?(docs_dir)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildLinuxX64
extend ActiveSupport::Concern
included do
register_expected_kind "linux_x64"
end
private
def linux_x64_asset_path(versioned)
path = full_path("#{versioned}-linux-x64.zip")
{ "linux_x64" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildMacArm64
extend ActiveSupport::Concern
included do
register_expected_kind "mac_arm64"
end
private
def mac_arm64_asset_path(versioned)
path = full_path("#{versioned}-mac-arm64.zip")
{ "mac_arm64" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildMacUniversal
extend ActiveSupport::Concern
included do
register_expected_kind "mac_universal"
end
private
def mac_universal_asset_path(versioned)
path = full_path("#{versioned}-mac-universal.zip")
{ "mac_universal" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildMacX64
extend ActiveSupport::Concern
included do
register_expected_kind "mac_x64"
end
private
def mac_x64_asset_path(versioned)
path = full_path("#{versioned}-mac-x64.zip")
{ "mac_x64" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,22 @@
module SoftwareUpdater
module Builds
module BuildSource
extend ActiveSupport::Concern
included do
register_expected_kind "source"
end
def source_ext
raise NotImplementedError, "#{self.class} must implement #source_ext"
end
private
def source_asset_path(versioned)
path = full_path("#{versioned}#{source_ext}")
{ "source" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,23 @@
module SoftwareUpdater
module Builds
module BuildWeb
extend ActiveSupport::Concern
included do
register_expected_kind "html"
register_prepare_step :prepare_web
end
private
def prepare_web(versioned)
extract_zip_to_dir("#{versioned}.html.zip", versioned)
end
def html_asset_path(versioned)
path = full_path(versioned)
{ "html" => path } if dir_exists?(versioned)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildWinX64
extend ActiveSupport::Concern
included do
register_expected_kind "win_x64"
end
private
def win_x64_asset_path(versioned)
path = full_path("#{versioned}-win-x64.zip")
{ "win_x64" => path } if File.file?(path)
end
end
end
end
@@ -0,0 +1,18 @@
module SoftwareUpdater
module Builds
module BuildWinX86
extend ActiveSupport::Concern
included do
register_expected_kind "win_x86"
end
private
def win_x86_asset_path(versioned)
path = full_path("#{versioned}-win-x86.zip")
{ "win_x86" => path } if File.file?(path)
end
end
end
end
@@ -4,13 +4,33 @@ module SoftwareUpdater
include ArchiveExtraction
include MetadataParsing
include SoftwarePersistence
include BinaryAttachment
class_methods do
def platform(value = nil)
@platform = value if value
@platform ||= name.demodulize.delete_suffix("Service").downcase
end
def label(value = nil)
@label = value if value
@label ||= platform.titleize
end
def expected_kinds
@expected_kinds ||= []
end
def register_expected_kind(kind)
expected_kinds << kind unless expected_kinds.include?(kind)
end
def prepare_steps
@prepare_steps ||= []
end
def register_prepare_step(method_name)
prepare_steps << method_name unless prepare_steps.include?(method_name)
end
end
def update(name, version)
@@ -27,7 +47,7 @@ module SoftwareUpdater
upsert_external_link(software.id, "Repository", repo_url) if repo_url.present?
release = create_release_if_not_exists(software_id: software.id, version: version)
sync_release_assets(release, asset_paths(versioned).merge(binary_asset_paths(versioned)))
sync_release_assets(release, collect_asset_paths(versioned))
release
end
end
@@ -35,15 +55,18 @@ module SoftwareUpdater
private
def prepare_files(versioned)
extract_zip_to_dir("#{versioned}.html.zip", versioned)
self.class.prepare_steps.each { |step| send(step, versioned) }
end
def parse_metadata(versioned)
parse_json_metadata(full_path("#{versioned}.metadata.json"))
end
def asset_paths(versioned)
{ "html" => full_path(versioned) }
def collect_asset_paths(versioned)
self.class.expected_kinds.each_with_object({}) do |kind, hash|
result = send(:"#{kind}_asset_path", versioned)
hash.merge!(result) if result
end
end
end
end
@@ -1,5 +1,10 @@
module SoftwareUpdater
class BevyService
include Updatable
include Builds::BuildWeb
include Builds::BuildWinX64
include Builds::BuildLinuxX64
label "Bevy"
end
end
@@ -1,15 +1,10 @@
module SoftwareUpdater
class C64Service
include Updatable
include Builds::BuildCartridge
private
label "C64"
def prepare_files(_versioned)
# a c64 buildhez nem tartozik html zip, csak a .prg fájl
end
def asset_paths(versioned)
{ "cartridge" => full_path("#{versioned}.prg") }
end
def cartridge_ext = ".prg"
end
end
@@ -1,5 +1,13 @@
module SoftwareUpdater
class EbitengineService
include Updatable
include Builds::BuildWeb
include Builds::BuildWinX86
include Builds::BuildWinX64
include Builds::BuildLinuxX64
include Builds::BuildMacX64
include Builds::BuildMacArm64
label "Ebitengine"
end
end
@@ -1,5 +1,12 @@
module SoftwareUpdater
class GodotService
include Updatable
include Builds::BuildWeb
include Builds::BuildWinX86
include Builds::BuildWinX64
include Builds::BuildLinuxX64
include Builds::BuildMacUniversal
label "Godot"
end
end
@@ -1,5 +1,11 @@
module SoftwareUpdater
class LoveService
include Updatable
include Builds::BuildWeb
include Builds::BuildWinX64
include Builds::BuildLinuxX64
include Builds::BuildMacUniversal
label "LÖVE"
end
end
@@ -1,5 +1,8 @@
module SoftwareUpdater
class PhaserService
include Updatable
include Builds::BuildWeb
label "Phaser"
end
end
@@ -1,32 +1,25 @@
module SoftwareUpdater
class Tic80Service
include Updatable
include Builds::BuildCartridge
include Builds::BuildSource
include Builds::BuildWeb
include Builds::BuildDocs
include Builds::BuildWinX64
include Builds::BuildLinuxX64
include Builds::BuildMacX64
label "TIC-80"
def cartridge_ext = ".tic"
def source_ext = ".lua"
private
# a docs opcionális: a régebbi sémájú tic80 pipeline-ok nem töltenek fel
# docs zipet, e nélkül is regisztrálni kell a többi assetet
def prepare_files(versioned)
extract_zip_to_dir("#{versioned}.html.zip", versioned)
docs_zip = "#{versioned}-docs.zip"
extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip))
end
def parse_metadata(versioned)
parse_lua_metadata(full_path("#{versioned}.lua"))
end
def asset_paths(versioned)
paths = {
"cartridge" => full_path("#{versioned}.tic"),
"source" => full_path("#{versioned}.lua"),
"html" => full_path(versioned)
}
docs_dir = "#{versioned}-docs"
paths["docs"] = full_path(docs_dir) if dir_exists?(docs_dir)
paths
end
def parse_lua_metadata(source_path)
metadata = {}
File.foreach(source_path) do |line|
+2
View File
@@ -15,6 +15,8 @@ Rails.application.routes.draw do
get "rss/releases", to: "rss#releases"
get "rss/howtos", to: "rss#howtos"
get "download", to: "downloads#show"
get "builds", to: "builds#index"
get "softwares/:name/builds", to: "software_builds#show"
end
get "update", to: "update#update"
get "file/*path", to: "files#show", format: false
@@ -0,0 +1,16 @@
require "rails_helper"
RSpec.describe Api::BuildsController, type: :request do
describe "GET /api/builds" do
it "returns the global build matrix" do
get "/api/builds"
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["platforms"]).to be_a(Hash)
expect(json["platforms"]["tic80"]["label"]).to eq("TIC-80")
expect(json["platforms"]["tic80"]["kinds"]).to include("cartridge")
expect(json["allKinds"]).to be_an(Array)
end
end
end
@@ -0,0 +1,29 @@
require "rails_helper"
RSpec.describe Api::SoftwareBuildsController, type: :request do
describe "GET /api/softwares/:name/builds" do
let!(:software) { create(:software, name: "test-game", platform: "love") }
let!(:release) { create(:release, software: software, version: "2.0.0") }
before do
ReleaseAsset.create!(release: release, kind: "html", path: "/test/html")
end
it "returns per-software build info" do
get "/api/softwares/test-game/builds"
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["platform"]).to eq("love")
expect(json["expected"]).to include("html", "win_x64")
expect(json["releases"]["2.0.0"]["actual"]).to include("html")
expect(json["releases"]["2.0.0"]["missing"]).to include("win_x64")
end
it "returns 404 for unknown software" do
get "/api/softwares/nonexistent/builds"
expect(response).to have_http_status(:not_found)
end
end
end
@@ -0,0 +1,50 @@
require "rails_helper"
RSpec.describe BuildsService do
describe "#index" do
subject(:result) { described_class.new.index }
it "returns all supported platforms with expected kinds" do
expect(result[:platforms]).to have_key("tic80")
expect(result[:platforms]["tic80"][:label]).to eq("TIC-80")
expect(result[:platforms]["tic80"][:kinds]).to include("cartridge", "html")
end
it "returns c64 with only cartridge" do
expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"])
end
it "returns allKinds matching ReleaseAsset::KINDS" do
expect(result[:allKinds]).to eq(ReleaseAsset::KINDS)
end
it "includes all supported platforms" do
UpdateService::SUPPORTED_PLATFORMS.each do |platform|
expect(result[:platforms]).to have_key(platform)
end
end
end
describe "#show" do
let!(:software) { create(:software, name: "test-game", platform: "love") }
let!(:release) { create(:release, software: software, version: "1.0.0") }
before do
ReleaseAsset.create!(release: release, kind: "html", path: "/test/html")
ReleaseAsset.create!(release: release, kind: "win_x64", path: "/test/win")
end
it "returns expected and actual kinds per release" do
result = described_class.new.show("test-game")
expect(result[:platform]).to eq("love")
expect(result[:expected]).to match_array(%w[html win_x64 linux_x64 mac_universal])
expect(result[:releases]["1.0.0"][:actual]).to match_array(%w[html win_x64])
expect(result[:releases]["1.0.0"][:missing]).to match_array(%w[linux_x64 mac_universal])
end
it "raises RecordNotFound for unknown software" do
expect { described_class.new.show("nonexistent") }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
@@ -63,11 +63,17 @@ RSpec.describe SoftwareUpdater::Tic80Service do
it "registers binary assets when slug zips are present" do
write_zip("#{versioned}-win-x64.zip", "game.exe" => "MZ")
write_zip("#{versioned}-mac-universal.zip", "game" => "bin")
write_zip("#{versioned}-mac-x64.zip", "game" => "bin")
release = described_class.new.update(name, version)
expect(asset_kinds(release)).to include("win_x64", "mac_universal")
expect(asset_kinds(release)).to include("win_x64", "mac_x64")
expect(asset_kinds(release)).not_to include("linux_x64")
end
it "exposes expected_kinds from included Build concerns" do
expect(described_class.expected_kinds).to match_array(
%w[cartridge source html docs win_x64 linux_x64 mac_x64]
)
end
end