mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
build: add build system (#23853)
This commit is contained in:
parent
711470de4e
commit
e972860fc9
6
examples/build_system/.gitignore
vendored
Normal file
6
examples/build_system/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Files and directories made by build.vsh:
|
||||
/target/
|
||||
/test.txt
|
||||
|
||||
# Pre-compiled build.vsh
|
||||
/build
|
58
examples/build_system/build.vsh
Executable file
58
examples/build_system/build.vsh
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env -S v run
|
||||
|
||||
import build
|
||||
import time
|
||||
|
||||
// Define variables that can be used to change tasks in the buildscript
|
||||
const app_name = 'hello'
|
||||
const program_args = 'World'
|
||||
const build_dir = 'target'
|
||||
|
||||
// Make the build context
|
||||
mut context := build.context(
|
||||
// Set the default task to `release` when no arguments are provided
|
||||
default: 'release'
|
||||
)
|
||||
|
||||
// Add a few simple tasks
|
||||
context.task(name: 'doc', run: |self| system('echo "Nothing to do"'))
|
||||
context.task(name: 'run', run: |self| system('v run . ${program_args}'))
|
||||
context.task(name: 'build', run: |self| system('v .'))
|
||||
context.task(name: 'build.prod', run: |self| system('v -prod -o ${app_name} .'))
|
||||
|
||||
// `_` to denote "private" tasks. Nothing stops the user from using it, but
|
||||
// this tells them that the task is not meant to be used by them.
|
||||
context.task(
|
||||
name: '_mkdirs'
|
||||
// The `help` field is displayed in `--tasks` to give a short summary of what the task does.
|
||||
help: 'Makes the directories used by the application'
|
||||
run: fn (self build.Task) ! {
|
||||
if !exists(build_dir) {
|
||||
mkdir_all(build_dir) or { panic(err) }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// This task will only run when the `test.txt` file is outdated
|
||||
context.artifact(
|
||||
name: 'test.txt'
|
||||
help: 'Generate test.txt'
|
||||
run: fn (self build.Task) ! {
|
||||
write_file('test.txt', time.now().str())!
|
||||
}
|
||||
)
|
||||
|
||||
// Add a more complex task
|
||||
context.task(
|
||||
name: 'release'
|
||||
help: 'Build the app in production mode, generates documentation, and releases the build on Git'
|
||||
depends: ['_mkdirs', 'doc', 'test.txt']
|
||||
run: fn (self build.Task) ! {
|
||||
system('v -prod -o ${build_dir}/${app_name} .')
|
||||
// Pretend we are using Git to publish the built file as a release here.
|
||||
}
|
||||
)
|
||||
|
||||
// Run the build context. This will iterate over os.args and each corresponding
|
||||
// task, skipping any arguments that start with a hyphen (-)
|
||||
context.run()
|
5
examples/build_system/main.v
Normal file
5
examples/build_system/main.v
Normal file
@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
fn main() {
|
||||
println('Hello, ${os.args[1]}!')
|
||||
}
|
52
vlib/build/README.md
Normal file
52
vlib/build/README.md
Normal file
@ -0,0 +1,52 @@
|
||||
## Description
|
||||
|
||||
`build` provides a small build system leveraging V(SH) for the buildscript.
|
||||
|
||||
## Example
|
||||
|
||||
> See also: [build_system example](https://github.com/vlang/v/tree/master/examples/build_system)
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v run
|
||||
|
||||
import build
|
||||
// .vsh automatically imports `os`, so you don't need this typically
|
||||
import os { system }
|
||||
|
||||
const app_name = 'vlang'
|
||||
const program_args = 'World'
|
||||
|
||||
mut context := build.context(
|
||||
// Set the default task to `release` when no arguments are provided
|
||||
default: 'release'
|
||||
)
|
||||
|
||||
context.task(name: 'doc', run: |self| system('v doc .'))
|
||||
context.task(name: 'run', run: |self| system('v run . ${program_args}'))
|
||||
context.task(name: 'build', run: |self| system('v .'))
|
||||
context.task(name: 'build.prod', run: |self| system('v -prod .'))
|
||||
|
||||
context.task(
|
||||
name: 'release'
|
||||
depends: ['doc']
|
||||
run: fn (self build.Task) ! {
|
||||
system('v -prod -o build/${app_name} .')
|
||||
// You could use Git to publish a release here too
|
||||
}
|
||||
)
|
||||
|
||||
context.run()
|
||||
```
|
||||
|
||||
## Pre-Compiling
|
||||
|
||||
Running VSH scripts requires V to compile the script before executing it, which can cause a delay
|
||||
between when you run `./build.vsh` and when the script actually starts executing.
|
||||
|
||||
If you want to fix this, you can "pre-compile" the buildscript by building the script, i.e, running
|
||||
`v -skip-running build.vsh`.
|
||||
|
||||
> You will need to rebuild every time you change the buildscript, and you should also add `/build`
|
||||
> to your `.gitignore`
|
||||
|
||||
> If you want maximum speed, you can also `v -prod -skip-running build.vsh`
|
180
vlib/build/build.v
Normal file
180
vlib/build/build.v
Normal file
@ -0,0 +1,180 @@
|
||||
module build
|
||||
|
||||
import os
|
||||
|
||||
@[heap; noinit]
|
||||
pub struct BuildContext {
|
||||
mut:
|
||||
// should_run caches the result of should_run from tasks.
|
||||
should_run map[string]bool
|
||||
tasks []Task
|
||||
pub mut:
|
||||
// default is the default task to run when no others are provided.
|
||||
default ?string
|
||||
}
|
||||
|
||||
@[heap; noinit]
|
||||
pub struct Task {
|
||||
run fn (Task) ! @[required]
|
||||
should_run fn (Task) !bool @[required]
|
||||
// repeatable controls whether or not this task can run multiple times per build cycle
|
||||
repeatable bool
|
||||
pub:
|
||||
name string
|
||||
help string
|
||||
depends []string
|
||||
mut:
|
||||
did_run bool
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct BuildContextParams {
|
||||
pub:
|
||||
default ?string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct TaskParams {
|
||||
pub:
|
||||
name string @[required]
|
||||
help string
|
||||
depends []string
|
||||
should_run fn (Task) !bool = |self| true
|
||||
run fn (Task) ! @[required]
|
||||
// repeatable controls whether or not this task can run multiple times per build cycle
|
||||
repeatable bool
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ArtifactParams {
|
||||
pub:
|
||||
name string @[required]
|
||||
help string
|
||||
depends []string
|
||||
should_run fn (Task) !bool = |self| !os.exists(self.name)
|
||||
run fn (Task) ! @[required]
|
||||
// repeatable controls whether or not this task can run multiple times per build cycle
|
||||
repeatable bool
|
||||
}
|
||||
|
||||
// context creates an empty BuildContext.
|
||||
pub fn context(params BuildContextParams) BuildContext {
|
||||
return BuildContext{
|
||||
default: params.default
|
||||
}
|
||||
}
|
||||
|
||||
// task creates a task for the given context.
|
||||
pub fn (mut context BuildContext) task(config TaskParams) {
|
||||
if context.get_task(config.name) != none {
|
||||
eprintln('error: task already exists with name `${config.name}`')
|
||||
exit(1)
|
||||
}
|
||||
context.tasks << Task{
|
||||
should_run: config.should_run
|
||||
run: config.run
|
||||
name: config.name
|
||||
help: config.help
|
||||
depends: config.depends
|
||||
}
|
||||
}
|
||||
|
||||
// artifact creates an artifact task for the given context.
|
||||
pub fn (mut context BuildContext) artifact(config ArtifactParams) {
|
||||
if context.get_task(config.name) != none {
|
||||
eprintln('error: task already exists with name `${config.name}`')
|
||||
exit(1)
|
||||
}
|
||||
context.tasks << Task{
|
||||
should_run: config.should_run
|
||||
run: config.run
|
||||
name: config.name
|
||||
help: config.help
|
||||
depends: config.depends
|
||||
repeatable: config.repeatable
|
||||
}
|
||||
}
|
||||
|
||||
// get_task gets the task with the given name.
|
||||
pub fn (mut context BuildContext) get_task(name string) ?&Task {
|
||||
for mut task in context.tasks {
|
||||
if task.name == name {
|
||||
return mut task
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// exec executes the task with the given name in the context.
|
||||
pub fn (mut context BuildContext) exec(name string) {
|
||||
if mut task := context.get_task(name) {
|
||||
task.exec(mut context)
|
||||
} else {
|
||||
eprintln('error: no such task: ${name}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// exec runs the given task and its dependencies
|
||||
pub fn (mut task Task) exec(mut context BuildContext) {
|
||||
if task.did_run && !task.repeatable {
|
||||
println(': ${task.name} (skipped)')
|
||||
return
|
||||
}
|
||||
|
||||
if task.name !in context.should_run {
|
||||
context.should_run[task.name] = task.should_run(task) or {
|
||||
eprintln('error: failed to call should_run for task `${task.name}`: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if !context.should_run[task.name] {
|
||||
println(': ${task.name} (skipped)')
|
||||
return
|
||||
}
|
||||
|
||||
for dep in task.depends {
|
||||
if dep == task.name {
|
||||
eprintln('error: cyclic task dependency detected, `${task.name}` depends on itself')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
context.exec(dep)
|
||||
}
|
||||
println(': ${task.name}')
|
||||
task.did_run = true
|
||||
task.run(task) or {
|
||||
eprintln('error: failed to run task `${task.name}`: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// run executes all tasks provided through os.args.
|
||||
pub fn (mut context BuildContext) run() {
|
||||
// filter out options
|
||||
mut tasks := os.args[1..].filter(|it| !it.starts_with('-'))
|
||||
|
||||
// check options
|
||||
if '--tasks' in os.args || '-tasks' in os.args {
|
||||
println('Tasks:')
|
||||
for _, task in context.tasks {
|
||||
println('- ${task.name}: ${task.help}')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if tasks.len == 0 {
|
||||
if context.default != none {
|
||||
tasks << context.default
|
||||
} else {
|
||||
eprintln('error: no task provided, run with `--tasks` for a list')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// execute tasks
|
||||
for arg in tasks {
|
||||
context.exec(arg)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user