commit a739960
shrub
·
2026-11-03 12:52:29 +0000 UTC
parent ac35ae5
add odin support
6 files changed,
+210,
-0
M
README
+2,
-0
1@@ -9,6 +9,8 @@ todo
2 ----
3 [X] core build engine
4 [X] higher level handling for C
5+[X] higher level handling for Odin
6 [ ] higher level handling for Zig
7 [ ] extensions for doing configure script style stuff easily(checking whats installed on the system)
8 [ ] become part of the revo stdlib!
9+
+161,
-0
1@@ -0,0 +1,161 @@
2+const engine = import "./build.rv"
3+
4+const settings = {
5+ compiler = {"odin"},
6+ package = ".",
7+ optimize = "",
8+ debug = :false,
9+ target = "",
10+ flags = {},
11+ collections = {},
12+ defines = {},
13+ deps = {},
14+}
15+
16+type Flags = table<string>
17+
18+#* override the global odin settings *#
19+pub fn configure(opts: table) -> :nil do
20+ for key in table.keys(opts) do
21+ if not table.key?(settings, key) do
22+ panic("unknown Odin setting:", key)
23+ end
24+
25+ settings[key] = opts[key]
26+ end
27+
28+ return :nil
29+end
30+
31+fn combined(opts: table, key: atom) -> table do
32+ return settings[key]:merge(table.key(opts, key) orelse {})
33+end
34+
35+#* track every .odin file in the entry package, including target-specific files *#
36+fn sources(dir: string) -> Flags do
37+ const paths: Flags = {}
38+
39+ for entry in fs.readdir(dir):unwrap() do
40+ if entry.kind == :file and entry.name:ends_with?(".odin") do
41+ paths:push(dir ~ "/" ~ entry.name)
42+ end
43+ end
44+
45+ return table.sort(paths)
46+end
47+
48+fn key_name(key: any) -> string do
49+ const name = string(key)
50+
51+ if typeof(key) == :atom do
52+ return name:sub(1, name:len() - 1)
53+ end
54+
55+ return name
56+end
57+
58+#*
59+ run a command into a temp path, then move it into place
60+ the returned command may panic, sorry
61+*#
62+fn action(path: string, args: Flags) -> (fn() -> :ok) do
63+ return fn() do
64+ const parts = path:split("/")
65+ parts:pop()
66+ const dir = table.join(parts, "/")
67+
68+ if dir != "" do
69+ fs.mkdir(dir, :true):unwrap()
70+ end
71+
72+ const temporary = path ~ ".tmp"
73+
74+ if fs.exists?(temporary) do
75+ fs.remove(temporary):unwrap()
76+ end
77+
78+ engine.cmd(args)
79+
80+ return fs.rename(temporary, path):unwrap()
81+ end
82+end
83+
84+fn output_rule(kind: atom, path: string, opts: table) -> string do
85+ const compiler = table.key(opts, :compiler) orelse settings.compiler
86+ const package = table.key(opts, :package) orelse settings.package
87+ const optimize = table.key(opts, :optimize) orelse settings.optimize
88+ const debug = table.key(opts, :debug) orelse settings.debug
89+ const target = table.key(opts, :target) orelse settings.target
90+
91+ const temporary = path ~ ".tmp"
92+ const modes = {executable = "exe", static = "static", shared = "shared"}
93+ const args = compiler:merge({"build", package})
94+ args:push("-build-mode:" ~ modes[kind], "-out:" ~ temporary)
95+
96+ if kind != :executable do
97+ args:push("-no-entry-point")
98+ end
99+
100+ if debug do
101+ args:push("-debug")
102+ end
103+
104+ if optimize != "" do
105+ args:push("-o:" ~ optimize)
106+ end
107+
108+ if target != "" do
109+ args:push("-target:" ~ target)
110+ end
111+
112+ const collections = combined(opts, :collections)
113+
114+ for key in table.keys(collections) do
115+ const name = key_name(key)
116+ args:push("-collection:" ~ name ~ "=" ~ collections[key])
117+ end
118+
119+ const defines = combined(opts, :defines)
120+
121+ for key in table.keys(defines) do
122+ const name = key_name(key)
123+ const define = defines[key]
124+ const value =
125+ if typeof(define) == :string do
126+ define
127+ end else do
128+ json.encode(define):unwrap()
129+ end
130+
131+ args:push("-define:" ~ name ~ "=" ~ value)
132+ end
133+
134+ const command = args:merge(combined(opts, :flags))
135+ const inputs = combined(opts, :deps)
136+ :merge(sources(package))
137+ :merge({package, argv[0]})
138+
139+ engine.rule(path, inputs, action(path, command))
140+
141+ const prefixes = {executable = "odin ", static = "odin static ", shared = "odin shared "}
142+ const label = prefixes[kind] ~ path
143+ engine.describe(path, label)
144+
145+ return path
146+end
147+
148+#* build an executable from an odin package directory *#
149+pub fn executable(path: string, opts: table) -> string
150+ return output_rule(:executable, path, opts)
151+
152+#* build a static library *#
153+pub fn static_library(path: string, opts: table) -> string
154+ return output_rule(:static, path, opts)
155+
156+#* build a shared library *#
157+pub fn shared_library(path: string, opts: table) -> string
158+ return output_rule(:shared, path, opts)
159+
160+pub fn build(outputs: table<string>) -> :ok do
161+ return engine.run(outputs)
162+end
+1,
-0
1@@ -0,0 +1 @@
2+/out/
+17,
-0
1@@ -0,0 +1,17 @@
2+package main
3+
4+add :: proc(a, b: int) -> int {
5+ return a + b
6+}
7+
8+subtract :: proc(a, b: int) -> int {
9+ return a - b
10+}
11+
12+multiply :: proc(a, b: int) -> int {
13+ return a * b
14+}
15+
16+divide :: proc(a, b: int) -> int {
17+ return a / b
18+}
+16,
-0
1@@ -0,0 +1,16 @@
2+package main
3+
4+import "core:fmt"
5+
6+SHOW_DETAILS :: #config(SHOW_DETAILS, false)
7+
8+main :: proc() {
9+ when SHOW_DETAILS {
10+ fmt.println("HELLO FROM ODIN BUILT WITH RVB!")
11+ }
12+
13+ fmt.printf("6 + 3 = %d\n", add(6, 3))
14+ fmt.printf("6 - 3 = %d\n", subtract(6, 3))
15+ fmt.printf("6 * 3 = %d\n", multiply(6, 3))
16+ fmt.printf("6 / 3 = %d\n", divide(6, 3))
17+}
+13,
-0
1@@ -0,0 +1,13 @@
2+const odin = import "../../buildodin.rv"
3+
4+odin.configure({
5+ optimize = "speed",
6+})
7+
8+const app = odin.executable("out/calculator", {
9+ package = ".",
10+ debug = :true,
11+ defines = {SHOW_DETAILS = :true},
12+})
13+
14+odin.build({app})