Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a step to CI to validate all the workspace crates are set to the correct versions #443

Merged
merged 6 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ on:
- master

jobs:
validate:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: ok-nick/[email protected]

- name: Validate Crate Versions
run: lune run validate-crate-versions

build:

runs-on: ubuntu-latest
Expand Down
81 changes: 81 additions & 0 deletions .lune/semver.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
--!strict
local SemVer = {}
SemVer.__index = SemVer

export type SemVer = {
major: number,
minor: number?,
patch: number?,
}

local function compare(a: number, b: number): number
if a > b then
return 1
elseif a < b then
return -1
end

return 0
end

function SemVer.__tostring(self: SemVer): string
return string.format("%i.%i.%i", self.major, self.minor or 0, self.patch or 0)
end

--[[
Constructs a new SemVer from the provided parts.
]]
function SemVer.new(major: number, minor: number?, patch: number?): SemVer
return (setmetatable({
major = major,
minor = minor,
patch = patch,
}, SemVer) :: any) :: SemVer
end

--[[
Parses `version` into a SemVer.
]]
function SemVer.parse(version: string)
local major, minor, patch, _ = version:match("^(%d+)%.(%d+)%.(%d+)(.*)$")
local realVersion = {
major = tonumber(major),
minor = tonumber(minor),
patch = tonumber(patch),
}
if realVersion.major == nil then
error(`could not parse major version from '{version}'`, 2)
end
if minor and realVersion.minor == nil then
error(`could not parse minor version from '{version}'`, 2)
end
if patch and realVersion.patch == nil then
error(`could not parse patch version from '{version}'`, 2)
end

return (setmetatable(realVersion, SemVer) :: any) :: SemVer
end

--[[
Compares two SemVers and returns their status compared to one another.

If `1` is returned, a is 'newer' than b.
If `-1` is returned, a is 'older' than b.
If `0` is returned, they are identical.
]]
function SemVer.compare(a: SemVer, b: SemVer)
local major = compare(a.major, b.major)
local minor = compare(a.minor or 0, b.minor or 0)

if major ~= 0 then
return major
end

if minor ~= 0 then
return minor
end

return compare(a.patch or 0, b.patch or 0)
end

return SemVer
107 changes: 107 additions & 0 deletions .lune/validate-crate-versions.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
--!strict
--#selene: allow(incorrect_standard_library_use)

local IGNORE_CRATE_LIST = {
"rbx_util",
"rbx_reflector",
}

local fs = require("@lune/fs")
local serde = require("@lune/serde")
local stdio = require("@lune/stdio")
local process = require("@lune/process")

local SemVer = require("semver")

type WorkspaceCargo = {
workspace: {
members: { string },
},
}

type CrateCargo = {
package: {
name: string,
version: string,
},
dependencies: { [string]: Dependency },
["dev-dependencies"]: { [string]: Dependency }?,
}

type Dependency = string | { version: string?, path: string?, features: { string }, optional: boolean? }

local function warn(...)
stdio.write(`[{stdio.color("yellow")}WARN{stdio.color("reset")}] `)
print(...)
end

local function processDependencies(dependency_list: { [string]: Dependency }, output: { [string]: Dependency })
for name, dependency in dependency_list do
if typeof(dependency) == "string" then
continue
end
if dependency.path then
output[name] = dependency
end
end
end

local workspace: WorkspaceCargo = serde.decode("toml", fs.readFile("Cargo.toml"))

local crate_info = {}

for _, crate_name in workspace.workspace.members do
if table.find(IGNORE_CRATE_LIST, crate_name) then
continue
end
local cargo: CrateCargo = serde.decode("toml", fs.readFile(`{crate_name}/Cargo.toml`))
local dependencies = {}
local dev_dependencies = {}
processDependencies(cargo.dependencies, dependencies)
if cargo.package["dev-dependencies"] then
processDependencies(cargo["dev-dependencies"] :: any, dev_dependencies)
end
crate_info[crate_name] = {
version = SemVer.parse(cargo.package.version),
dependencies = dependencies,
dev_dependencies = dev_dependencies,
}
end

table.freeze(crate_info)

local all_ok = true

for crate_name, cargo in crate_info do
for name, dependency in cargo.dependencies do
if typeof(dependency) == "string" then
error("invariant: string dependency made it into path list")
end
if not crate_info[name] then
warn(`Dependency {name} of crate {crate_name} has a path but is not local to this workspace.`)
all_ok = false
continue
end
if not dependency.version then
warn(`Dependency {name} of crate {crate_name} has a path but no version specified. Please fix this.`)
all_ok = false
continue
end
local dependency_version = SemVer.parse(dependency.version :: string)
local cmp = SemVer.compare(crate_info[name].version, dependency_version)
if cmp == 0 then
continue
else
all_ok = false
warn(
`Dependency {name} of crate {crate_name} has a version mismatch. Current version: {dependency_version}. Should be: {crate_info[name].version}`
)
end
end
end

if all_ok then
process.exit(0)
else
process.exit(1)
end
1 change: 1 addition & 0 deletions aftman.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ rojo = "rojo-rbx/[email protected]"
run-in-roblox = "rojo-rbx/[email protected]"
selene = "Kampfkarren/[email protected]"
stylua = "JohnnyMorganz/[email protected]"
lune = "lune-org/[email protected]"