-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjustfile
More file actions
435 lines (359 loc) · 13.8 KB
/
justfile
File metadata and controls
435 lines (359 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#!/usr/bin/env just
# --- Settings --- #
set shell := ["nu", "-c"]
set positional-arguments := true
set allow-duplicate-variables := true
set windows-shell := ["nu", "-c"]
set dotenv-load := true
# --- Variables --- #
project_root := justfile_directory()
output_directory := project_root + "/dist"
build_directory := `cargo metadata --format-version 1 | jq -r .target_directory`
system := `rustc --version --verbose | grep '^host:' | awk '{print $2}'`
main_package := "lic"
# ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ #
# Recipes #
# ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ #
[doc('List all available recipes')]
default:
@just --list
# --- Build & Check --- #
[doc('Check workspace for compilation errors')]
[group('build')]
check:
@echo "🔎 Checking workspace..."
cargo check --workspace
[doc('Build workspace in debug mode')]
[group('build')]
build target="aarch64-apple-darwin" package=(main_package):
@echo "🔨 Building workspace (debug)..."
cargo build --workspace --bin '{{main_package}}' --target '{{target}}'
[doc('Build workspace in release mode')]
[group('build')]
build-release target=(system) package=(main_package):
@echo "🚀 Building workspace (release) for {{target}}…"
cargo build --workspace --release --bin '{{main_package}}' --target '{{target}}'
# --- Packaging --- #
[doc('Package release binary with completions for distribution')]
[group('packaging')]
package target=(system):
#!/usr/bin/env nu
def build_error [msg: string, error?: record] {
if ($error != null) {
let annotated_error = ($error | upsert msg $'($msg): ($error.msg)')
$annotated_error.rendered | print --stderr
} else {
(error make --unspanned { msg: $msg }) | print --stderr
}
exit 1
}
let target = '{{target}}'
let prime = '{{main_package}}'
let out = "{{output_directory}}"
let artifact_dir = $'{{build_directory}}/($target)/release'
try {
just build-release $target
print "📦 Packaging release binary…"
# Windows the only one that has an executable extension
let ext = if ($target | str contains 'windows-msvc') { '.exe' } else { '' }
# Example: package-triplet
let qualified_name = $"($prime)-($target)"
let bin_path = $'($artifact_dir)/($prime)($ext)' # Where rust puts the binary artifact
let out_path = $'($out)/($qualified_name)($ext)'
# Create output directory structure
try {
mkdir $out
} catch {|e|
build_error $"Failed to create directory: ($out)" $e
}
# Copy completion scripts
let completions = ['lichenn.bash', 'lichenn.elv', 'lichenn.fish', '_lichenn.ps1', '_lichenn']
for completion in $completions {
let src = $'($artifact_dir)/($completion)'
let dst = $'($out)/($completion)'
if ($src | path exists) {
try {
cp --force $src $dst # Using force here because default nu copy only works with existing files otherwise
print $"('Successfully copied to destination' | ansi gradient --fgstart '0x00ff00' --fgend '0xff0080' --bgstart '0x1a1a1a' --bgend '0x0d0d0d') ($src)"
} catch {|e|
build_error $"Failed to copy completion script ($src)" $e
}
} else {
print --stderr $"Warning: completion script missing: ($src)"
}
}
# Copy main binary
try {
cp --force $bin_path $out_path
print $"('Successfully copied to destination' | ansi gradient --fgstart '0x00ff00' --fgend '0xff0080' --bgstart '0x1a1a1a' --bgend '0x0d0d0d') ($bin_path)"
} catch { |e|
build_error $"Failed to copy binary ($bin_path)" $e
}
} catch { |e|
build_error "Packaging failed" $e
}
[doc('Generate checksums for distribution files')]
[group('packaging')]
checksum directory=(output_directory):
#!/usr/bin/env nu
def build_error [msg: string, error?: record] {
if ($error != null) {
let annotated_error = ($error | upsert msg $'($msg): ($error.msg)')
$annotated_error.rendered | print --stderr
exit 1
} else {
(error make --unspanned { msg: $msg }) | print --stderr
exit 1
}
}
let dir = '{{directory}}'
print $"🔒 Generating checksums in '($dir)'…"
# Validate directory exists
if not ($dir | path exists) {
build_error $"'($dir)' is not a directory."
}
try {
cd $dir
# Remove existing checksum files
try {
glob '*.sum' | each { |file| rm $file }
} catch {
# Ignore errors if no .sum files exist
}
# Get all files except checksum files
let files = ls | where type == file | where name !~ '\.sum$' | get name
if (($files | length) == 0) {
print --stderr "Warning: No files found to checksum"
return
}
# Generate SHA256 checksums
try {
let sha256_results = $files | each { |file|
let hash = (open --raw $file | hash sha256)
$"($hash) ./($file | path basename)"
}
$sha256_results | str join (char newline) | save SHA256.sum
} catch {|e|
build_error $"Failed to generate SHA256 checksums" $e
}
# Generate MD5 checksums
try {
let md5_results = $files | each { |file|
let hash = (open --raw $file | hash md5)
$"($hash) ./($file | path basename)"
}
$md5_results | str join (char newline) | save MD5.sum
} catch {|e|
build_error $"Failed to generate MD5 checksums" $e
}
# Generate BLAKE3 checksums (using b3sum command)
try {
let b3_results = $files | each { |file|
let result = (run-external 'b3sum' $file | complete)
if $result.exit_code != 0 {
build_error $"b3sum failed for ($file): ($result.stderr)"
}
let hash = ($result.stdout | str trim | split row ' ' | get 0)
$"($hash) ./($file | path basename)"
}
$b3_results | str join (char newline) | save BLAKE3.sum
} catch {|e|
build_error $"Failed to generate BLAKE3 checksums" $e
}
print $"✅ Checksums created in '($dir)'"
} catch {|e|
build_error $"Checksum generation failed" $e
}
[doc('Compress all release packages into tar.gz archives')]
[group('packaging')]
compress directory=(output_directory):
#!/usr/bin/env nu
def build_error [msg: string, error?: record] {
if ($error != null) {
let annotated_error = ($error | upsert msg $'($msg): ($error.msg)')
$annotated_error.rendered | print --stderr
exit 1
} else {
(error make --unspanned { msg: $msg }) | print --stderr
exit 1
}
}
print "🗜️ Compressing release packages..."
let dir = '{{directory}}'
if not ($dir | path exists) {
build_error $"Directory '($dir)' does not exist"
}
try {
# Find all package directories
let package_dirs = ls $dir | where type == dir | get name
if (($package_dirs | length) == 0) {
print "No package directories found to compress"
return
}
for pkg_dir in $package_dirs {
let pkg_name = ($pkg_dir | path basename)
print $"Compressing package: ($pkg_name)"
try {
let parent_dir = ($pkg_dir | path dirname)
let archive_name = $'($pkg_dir).tar.gz'
# Use tar command to create compressed archive
let result = (run-external 'tar' '-czf' $archive_name '-C' $parent_dir $pkg_name | complete)
if $result.exit_code != 0 {
build_error $"Failed to create archive for ($pkg_name): ($result.stderr)"
}
print $"✅ Successfully compressed ($pkg_name)"
} catch{ |e|
build_error $"Compression failed for ($pkg_name)" $e
}
}
print "🎉 All packages compressed successfully!"
} catch {|e|
build_error $"Compression process failed" $e
}
[doc('Complete release pipeline: build, checksum, and compress')]
[group('packaging')]
release: build-release
just checksum
# --- Execution --- #
[doc('Run application in debug mode')]
[group('execution')]
run package=(main_package) +args="":
@echo "▶️ Running {{package}} (debug)..."
cargo run --bin '{{package}}' -- '$@'
[doc('Run application in release mode')]
[group('execution')]
run-release package=(main_package) +args="":
@echo "▶️ Running '{{package}}' (release)..."
cargo run --bin '{{package}}' --release -- '$@'
# --- Testing --- #
[doc('Run all workspace tests')]
[group('testing')]
test:
@echo "🧪 Running workspace tests..."
cargo test --workspace
[doc('Run workspace tests with additional arguments')]
[group('testing')]
test-with +args:
@echo "🧪 Running workspace tests with args: '$@'"
cargo test --workspace -- '$@'
# --- Code Quality --- #
[doc('Format all Rust code in the workspace')]
[group('quality')]
fmt:
@echo "💅 Formatting Rust code..."
cargo fmt
[doc('Check if Rust code is properly formatted')]
[group('quality')]
fmt-check:
@echo "💅 Checking Rust code formatting..."
cargo fmt
[doc('Lint code with Clippy in debug mode')]
[group('quality')]
lint:
@echo "🧹 Linting with Clippy (debug)..."
cargo clippy --workspace -- -D warnings
[doc('Automatically fix Clippy lints where possible')]
[group('quality')]
lint-fix:
@echo "🩹 Fixing Clippy lints..."
cargo clippy --workspace --fix --allow-dirty --allow-staged
# --- Documentation --- #
[doc('Generate project documentation')]
[group('common')]
doc:
@echo "📚 Generating documentation..."
cargo doc --workspace --no-deps
[doc('Generate and open project documentation in browser')]
[group('common')]
doc-open: doc
@echo "📚 Opening documentation in browser..."
cargo doc --workspace --no-deps --open
# --- Maintenance --- #
[doc('Extract release notes from changelog for specified tag')]
[group('common')]
create-notes raw_tag outfile changelog:
#!/usr/bin/env nu
def build_error [msg: string, error?: record] {
if ($error != null) {
let annotated_error = ($error | upsert msg $'($msg): ($error.msg)')
$annotated_error.rendered | print --stderr
exit 1
} else {
(error make --unspanned { msg: $msg }) | print --stderr
exit 1
}
}
let tag_v = '{{raw_tag}}'
let tag = ($tag_v | str replace --regex '^v' '') # Remove prefix v
let outfile = '{{outfile}}'
let changelog_file = '{{changelog}}'
try {
# Verify changelog exists
if not ($changelog_file | path exists) {
build_error $"($changelog_file) not found."
}
print $"Extracting notes for tag: ($tag_v) (searching for section [($tag)])"
# Write header to output file
"# What's new\n" | save $outfile
# Read and process changelog
let content = (open $changelog_file | lines)
let section_header = $"## [($tag)]"
# Find the start of the target section
let start_idx = ($content | enumerate | where item == $section_header | get index | first)
if ($start_idx | is-empty) {
build_error $"Could not find section header ($section_header) in ($changelog_file)"
}
# Find the end of the target section (next ## [ header)
let remaining_lines = ($content | skip ($start_idx + 1))
let next_section_idx = ($remaining_lines | enumerate | where item =~ '^## \[' | get index | first)
let section_lines = if ($next_section_idx | is-empty) {
$remaining_lines
} else {
$remaining_lines | take $next_section_idx
}
# Append section content to output file
$section_lines | str join (char newline) | save --append $outfile
# Check if output file has meaningful content
let output_size = (open $outfile | str length)
if $output_size > 20 { # More than just the header
print $"Successfully extracted release notes to '($outfile)'."
} else {
print --stderr $"Warning: '($outfile)' appears empty. Is '($section_header)' present in '($changelog_file)'?"
}
} catch { |e|
build_error $"Failed to extract release notes:" $e
}
[doc('Update Cargo dependencies')]
[group('maintenance')]
update:
@echo "🔄 Updating dependencies..."
cargo update
[doc('Clean build artifacts')]
[group('maintenance')]
clean:
@echo "🧹 Cleaning build artifacts..."
cargo clean
# --- Installation --- #
[doc('Build and install binary to system')]
[group('installation')]
install package=(main_package): build-release
@echo "💾 Installing {{main_package}} binary..."
cargo install --bin '{{package}}'
[doc('Force install binary')]
[group('installation')]
install-force package=(main_package): build-release
@echo "💾 Force installing {{main_package}} binary..."
cargo install --bin '{{package}}' --force
# --- Aliases --- #
alias b := build
alias br := build-release
alias c := check
alias t := test
alias f := fmt
alias l := lint
alias lf := lint-fix
alias cl := clean
alias up := update
alias i := install
alias ifo := install-force
alias rr := run-release