mirror of
https://github.com/gechandesu/v-cross-compilation-example.git
synced 2026-01-02 14:39:35 +03:00
Rework build scripts (close #5)
- Separate cross-compilation logic to crosscompile.vsh - Manage Docker in make.vsh, no more manual running `docker build`, etc. - Add MANUAL.md instruction - Enchance Dockerfile
This commit is contained in:
@@ -10,8 +10,7 @@ RUN apt-get update && \
|
|||||||
apt-get clean && rm -rf /var/cache/apt/archives/* && rm -rf /var/lib/apt/lists/*
|
apt-get clean && rm -rf /var/cache/apt/archives/* && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN git clone --depth=1 https://github.com/vlang/v /opt/v && \
|
RUN git clone --depth=1 https://github.com/vlang/v /opt/v && \
|
||||||
cd /opt/v && \
|
make -C /opt/v && \
|
||||||
make && \
|
|
||||||
/opt/v/v symlink && \
|
/opt/v/v symlink && \
|
||||||
v version
|
v version
|
||||||
|
|
||||||
@@ -36,4 +35,5 @@ WORKDIR /app
|
|||||||
|
|
||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
|
|
||||||
ENV VMODULES=/tmp/.vmodules
|
ENV VMODULES=/tmp/vmodules
|
||||||
|
ENV VCACHE=/tmp/vcache
|
||||||
|
|||||||
138
MANUAL.md
Normal file
138
MANUAL.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# Manual cross-compilation
|
||||||
|
|
||||||
|
This repository contains ready-made scripts that allow you to describe the
|
||||||
|
desired results and obtain them simply by running `./make.vsh`. However, to
|
||||||
|
better understand the process, it's worth considering the manual build.
|
||||||
|
This file describes the algorithm of actions automated in the aforementioned
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
## Prepare the environment
|
||||||
|
|
||||||
|
We want reproducible builds. We also don't want to clutter our computer with
|
||||||
|
things needed exclusively for cross-compilation. Besides manipulating certain
|
||||||
|
packages in the OS can inadvertently damage the system.
|
||||||
|
|
||||||
|
Docker will help us achieve all our goals.
|
||||||
|
[Install it](https://docs.docker.com/get-started/get-docker/) if you haven't
|
||||||
|
already. Containers will ensure reproducible builds, as they will always run
|
||||||
|
in the same environment.
|
||||||
|
|
||||||
|
## Let's begin
|
||||||
|
|
||||||
|
Create a V programm. Just initialize empty V project in some dir:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ mkdir crossv
|
||||||
|
$ cd crossv
|
||||||
|
$ v init
|
||||||
|
Input your project description: Cross-compilation example
|
||||||
|
Input your project version: (0.0.0)
|
||||||
|
Input your project license: (MIT)
|
||||||
|
Initialising ...
|
||||||
|
Created binary (application) project `crossv`
|
||||||
|
```
|
||||||
|
|
||||||
|
Contents of `main.v`:
|
||||||
|
|
||||||
|
```v
|
||||||
|
module main
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('Hello World!')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There is already an example Dockerfile in this repository, so here I will focus
|
||||||
|
on CLI. So let's run Debian Linux in container with current directory mounted:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -ti -v .:/app -w /app debian:trixie
|
||||||
|
```
|
||||||
|
|
||||||
|
See https://docs.docker.com/reference/cli/docker/container/run/ for details.
|
||||||
|
|
||||||
|
Now we will run shell commands inside container.
|
||||||
|
|
||||||
|
## Setup V compiler in container
|
||||||
|
|
||||||
|
Install prerequisistes:
|
||||||
|
|
||||||
|
```
|
||||||
|
apt update
|
||||||
|
apt install -y --no-install-recommends --no-install-suggests build-essential git ca-certificates file
|
||||||
|
```
|
||||||
|
|
||||||
|
Download and bootstrap V compiler:
|
||||||
|
|
||||||
|
```
|
||||||
|
export VMODULES=/tmp/vmodules VCACHE=/tmp/vcache
|
||||||
|
git clone --depth=1 https://github.com/vlang/v /opt/v && make -C /opt/v && /opt/v/v symlink
|
||||||
|
```
|
||||||
|
|
||||||
|
After this `v` command should work. Try:
|
||||||
|
|
||||||
|
```
|
||||||
|
v version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cross-compile to ARM64 (AArch64)
|
||||||
|
|
||||||
|
Your host is most likely an x86_64 computer. For the sake of example, let's
|
||||||
|
compile our Linux program for the AArch64 architecture.
|
||||||
|
|
||||||
|
First we need to add build requirements. Debian already have an excellent
|
||||||
|
[cross-compiling](//wiki.debian.org/CrossCompiling) support.
|
||||||
|
|
||||||
|
Prepate Debian package manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
dpkg --add-architecture arm64
|
||||||
|
apt update
|
||||||
|
```
|
||||||
|
|
||||||
|
We need to install `crossbuild-essential-arm64` package:
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install -y --no-install-recommends --no-install-suggests crossbuild-essential-arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
Also there is packages for some other architectures which Debian supports:
|
||||||
|
https://packages.debian.org/search?keywords=crossbuild-essential&searchon=names&suite=stable§ion=all
|
||||||
|
|
||||||
|
Now we have a GCC cross-compiler and some common libraries for ARM64.
|
||||||
|
|
||||||
|
To compile out project just run:
|
||||||
|
|
||||||
|
```
|
||||||
|
v -prod -cc aarch64-linux-gnu-gcc -o hello .
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's make sure we've built the correct executable using the `file` utility:
|
||||||
|
|
||||||
|
```console
|
||||||
|
# file hello
|
||||||
|
hello: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=e9cfdee9abe5a80c304d489f243fbc60a22d93de, for GNU/Linux 3.7.0, not stripped
|
||||||
|
```
|
||||||
|
|
||||||
|
Binary is dynamically linked. To produce statically linked binary add `-cflags -static` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
v -prod -cc aarch64-linux-gnu-gcc -cflags -static -o hello .
|
||||||
|
```
|
||||||
|
|
||||||
|
Done.
|
||||||
|
|
||||||
|
Since we were operating as the root user inside the container, it's worth
|
||||||
|
changing the file owner:
|
||||||
|
|
||||||
|
```
|
||||||
|
chown 1000:1000 hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `1000:1000` with your actual `UID:GID` pair on host system.
|
||||||
|
|
||||||
|
Now we can exit from container:
|
||||||
|
|
||||||
|
```
|
||||||
|
exit
|
||||||
|
```
|
||||||
54
README.md
54
README.md
@@ -14,7 +14,7 @@ Produced binaries:
|
|||||||
|
|
||||||
* Linux: `amd64`, `arm64`, `arm32` (`armhf`), `ppc64le`, `s390x`, `riscv64`
|
* Linux: `amd64`, `arm64`, `arm32` (`armhf`), `ppc64le`, `s390x`, `riscv64`
|
||||||
* Windows: `amd64`
|
* Windows: `amd64`
|
||||||
* FreeBSD: `amd64`
|
* ~~FreeBSD: `amd64`~~ (disabled for now)
|
||||||
|
|
||||||
The example programm is just `Hello World!`. For complex programs you may need
|
The example programm is just `Hello World!`. For complex programs you may need
|
||||||
to add more dependencies in build container.
|
to add more dependencies in build container.
|
||||||
@@ -23,60 +23,30 @@ I relied on Debian's excellent cross-compilation support (see the Dockerfile),
|
|||||||
but with some elbow grease, you can compile the program for other architectures
|
but with some elbow grease, you can compile the program for other architectures
|
||||||
and operating systems.
|
and operating systems.
|
||||||
|
|
||||||
Build:
|
**Build**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build . -t vlang-cross:latest-trixie
|
./make.vsh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
make.vsh script will build the Docker image and run crosscompile.vsh inside
|
||||||
|
a container.
|
||||||
|
|
||||||
The container image is large (almost 3GiB) due to the number of libraries
|
The container image is large (almost 3GiB) due to the number of libraries
|
||||||
required for cross-compilation. The size could actually be reduced, but that's
|
required for cross-compilation. The size could actually be reduced, but that's
|
||||||
what Debian provides by default in the `crossbuild-essential-*` packages. For
|
what Debian provides by default in the `crossbuild-essential-*` packages. For
|
||||||
the same reason, building the image isn't very fast (up to ~3 minutes for me).
|
the same reason, building the image isn't very fast (up to ~3 minutes for me).
|
||||||
|
|
||||||
Start cross-compilation:
|
You may need change `docker_command` in `make.vsh` to `sudo docker` if your
|
||||||
|
host user does not have access to Docker daemon.
|
||||||
|
|
||||||
```
|
Look inside `release/` dir after compilation (:
|
||||||
docker run --rm -v .:/app vlang-cross:latest-trixie env DEBUG=1 ./make.vsh
|
|
||||||
```
|
|
||||||
|
|
||||||
then look inside `release/` dir (:
|
|
||||||
|
|
||||||
## Synopsis
|
|
||||||
|
|
||||||
You can run the make.vsh script in two ways:
|
|
||||||
|
|
||||||
```
|
|
||||||
./make.vsh
|
|
||||||
# or
|
|
||||||
v run make.vsh
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
Build script options:
|
|
||||||
-tasks List available tasks.
|
|
||||||
-help Print this help message and exit. Aliases: help, --help.
|
|
||||||
|
|
||||||
Build can be configured throught environment variables:
|
|
||||||
|
|
||||||
BUILD_PROG_NAME Name of the compiled program. By default the name is
|
|
||||||
parsed from v.mod.
|
|
||||||
BUILD_PROG_VERSION Version of the compiled program. By default the name
|
|
||||||
is parsed from v.mod.
|
|
||||||
BUILD_PROG_ENTRYPOINT The program entrypoint. Defaults to '.' (current dir).
|
|
||||||
BUILD_OUTPUT_DIR The directory where the build artifacts will be placed.
|
|
||||||
Defaults to './release'.
|
|
||||||
BUILD_SKIP_TARGETS List of build targets to skip. Expects comma-separated
|
|
||||||
list without whitespaces e.g. 'windows-amd64,linux-armhf'
|
|
||||||
BUILD_COMMON_VFLAGS The list of V flags is common for all targets. Expects
|
|
||||||
comma-separated list. Default is '-prod,-cross'.
|
|
||||||
BUILD_COMMON_CFLAGS Same as BUILD_COMMON_VFLAGS, but passed to underlying
|
|
||||||
C compiler. Default is '-static'.
|
|
||||||
DEBUG If set enables the verbose output as dimmed text.
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
* [MANUAL.md](MANUAL.md) in this repository.
|
||||||
* `v help build`
|
* `v help build`
|
||||||
* `v help build-c`
|
* `v help build-c`
|
||||||
* https://docs.vlang.io/cross-compilation.html
|
* https://docs.vlang.io/cross-compilation.html
|
||||||
|
|||||||
234
crosscompile.vsh
Executable file
234
crosscompile.vsh
Executable file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env -S v run
|
||||||
|
|
||||||
|
import arrays.parallel
|
||||||
|
import os
|
||||||
|
import os.cmdline
|
||||||
|
import term
|
||||||
|
import v.vmod
|
||||||
|
|
||||||
|
/*
|
||||||
|
SETTING BUILD TARGETS
|
||||||
|
|
||||||
|
All build targets must be defined in the build_targets const below.
|
||||||
|
Each target is a V struct. Fields are:
|
||||||
|
|
||||||
|
name string
|
||||||
|
The target name. Prefer to use https://wiki.osdev.org/Target_Triplet
|
||||||
|
Note that target name will be used in output file name. For example
|
||||||
|
the 'linux-riscv64' becomes to 'myprog-1.2.3-linux-riscv64'. This is
|
||||||
|
very common naming scheme for compiled program distributions.
|
||||||
|
|
||||||
|
cc string
|
||||||
|
C Compiler to use e.g. '/usr/bin/gcc', 'clang', etc.
|
||||||
|
|
||||||
|
vflags []string
|
||||||
|
cflags []string
|
||||||
|
ldflags []string
|
||||||
|
Flags which will be passed to V compiler. See `v help build-c` for info.
|
||||||
|
|
||||||
|
filename string
|
||||||
|
Output file naming pattern. By default is '%n-%v-%t'.
|
||||||
|
%n will be replaced with the program name (from v.mod by default)
|
||||||
|
%v will be replaced with the program version (also from v.mod)
|
||||||
|
%t will be replaced with the target name from `name` field.
|
||||||
|
For example this is useful for Windows builds: for target named
|
||||||
|
'windows-amd64' and '%n-%v-%t.exe' filename pattern value you will get
|
||||||
|
artifact named 'progname-1.2.3-windows-amd64.exe'.
|
||||||
|
|
||||||
|
See also Target struct definition below.
|
||||||
|
|
||||||
|
V'S SPECIAL ENVIRONMENT VARIABLES
|
||||||
|
|
||||||
|
VCROSS_COMPILER_NAME See vcross_compiler_name() in v.pref module.
|
||||||
|
VCROSS_LINKER_NAME See vcross_linker_name() in v.pref module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const build_targets = [
|
||||||
|
Target{
|
||||||
|
name: 'linux-amd64'
|
||||||
|
cc: 'gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'linux-arm64'
|
||||||
|
cc: 'aarch64-linux-gnu-gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'linux-armhf'
|
||||||
|
cc: 'arm-linux-gnueabihf-gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'linux-ppc64le'
|
||||||
|
cc: 'powerpc64le-linux-gnu-gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'linux-s390x'
|
||||||
|
cc: 's390x-linux-gnu-gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'linux-riscv64'
|
||||||
|
cc: 'riscv64-linux-gnu-gcc'
|
||||||
|
},
|
||||||
|
Target{
|
||||||
|
name: 'windows-amd64'
|
||||||
|
vflags: ['-os', 'windows']
|
||||||
|
filename: '%n-%v-%t.exe'
|
||||||
|
},
|
||||||
|
// FreeBSD build is buggy, disable it for now...
|
||||||
|
// Target{
|
||||||
|
// name: 'freebsd-amd64'
|
||||||
|
// vflags: ['-os', 'freebsd']
|
||||||
|
// },
|
||||||
|
]
|
||||||
|
|
||||||
|
struct Target {
|
||||||
|
name string
|
||||||
|
cc string
|
||||||
|
vflags []string
|
||||||
|
cflags []string
|
||||||
|
ldflags []string
|
||||||
|
filename string = '%n-%v-%t'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (target Target) output_file() string {
|
||||||
|
// vfmt off
|
||||||
|
return target.filename.replace_each([
|
||||||
|
'%n', build_config.program_name,
|
||||||
|
'%v', build_config.program_version,
|
||||||
|
'%t', target.name,
|
||||||
|
])
|
||||||
|
// vfmt on
|
||||||
|
}
|
||||||
|
|
||||||
|
const build_config = BuildConfig.new()
|
||||||
|
|
||||||
|
struct BuildConfig {
|
||||||
|
program_name string
|
||||||
|
program_version string
|
||||||
|
program_entrypoint string
|
||||||
|
output_dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn BuildConfig.new() BuildConfig {
|
||||||
|
manifest := vmod.decode(@VMOD_FILE) or { vmod.Manifest{} }
|
||||||
|
return BuildConfig{
|
||||||
|
program_name: os.getenv_opt('BUILD_PROG_NAME') or { manifest.name }
|
||||||
|
program_version: os.getenv_opt('BUILD_PROG_VERSION') or { manifest.version }
|
||||||
|
program_entrypoint: os.getenv_opt('BUILD_PROG_ENTRYPOINT') or { '.' }
|
||||||
|
output_dir: os.abs_path(os.norm_path(os.getenv_opt('BUILD_OUTPUT_DIR') or {
|
||||||
|
'release'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_build(build_target Target) ! {
|
||||||
|
artifact := os.join_path_single(build_config.output_dir, build_target.output_file())
|
||||||
|
|
||||||
|
eprintln(term.bold('Building artifact: ${artifact}'))
|
||||||
|
|
||||||
|
os.mkdir_all(os.dir(artifact)) or {}
|
||||||
|
|
||||||
|
mut vargs := []string{}
|
||||||
|
if build_target.cc != '' {
|
||||||
|
vargs << ['-cc', build_target.cc]
|
||||||
|
}
|
||||||
|
for vflag in build_target.vflags {
|
||||||
|
vargs << vflag
|
||||||
|
}
|
||||||
|
for cflag in build_target.cflags {
|
||||||
|
vargs << ['-cflags', cflag]
|
||||||
|
}
|
||||||
|
for ldflag in build_target.ldflags {
|
||||||
|
vargs << ['-ldflags', ldflag]
|
||||||
|
}
|
||||||
|
vargs << ['-o', artifact]
|
||||||
|
vargs << build_config.program_entrypoint
|
||||||
|
|
||||||
|
execute_command(@VEXE, vargs)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_command(executable string, args []string) ! {
|
||||||
|
path := os.find_abs_path_of_executable(executable) or { os.norm_path(executable) }
|
||||||
|
printdbg("Run '${path}' with arguments: ${args}")
|
||||||
|
mut proc := os.new_process(path)
|
||||||
|
proc.set_args(args)
|
||||||
|
proc.set_work_folder(os.getwd())
|
||||||
|
proc.run()
|
||||||
|
proc.wait()
|
||||||
|
if proc.status == .exited && proc.code != 0 {
|
||||||
|
return error('Command ${term.bold(path)} exited with non-zero code ${proc.code}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printdbg(s string) {
|
||||||
|
if os.getenv('DEBUG') !in ['', '0', 'false', 'no'] {
|
||||||
|
eprintln(term.dim(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@[noreturn]
|
||||||
|
fn errexit(s string) {
|
||||||
|
eprintln(term.failed('Error: ${s}'))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
args := os.args[1..]
|
||||||
|
|
||||||
|
mut targets := map[string]Target{}
|
||||||
|
for target in build_targets {
|
||||||
|
targets[target.name] = target
|
||||||
|
}
|
||||||
|
|
||||||
|
options := cmdline.only_options(args)
|
||||||
|
if args.contains('help') || options.contains('-help') || options.contains('--help') {
|
||||||
|
println(help_text)
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
if options.contains('-targets') {
|
||||||
|
for name, _ in targets {
|
||||||
|
println(name)
|
||||||
|
}
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
if options.contains('-release') {
|
||||||
|
os.setenv('VFLAGS', '${os.getenv('VFLAGS')} -prod -cflags -static'.trim_space(),
|
||||||
|
true)
|
||||||
|
}
|
||||||
|
|
||||||
|
printdbg('Args: ${args}')
|
||||||
|
printdbg('VFLAGS=${os.getenv('VFLAGS')}')
|
||||||
|
printdbg('VJOBS=${os.getenv('VJOBS')}')
|
||||||
|
printdbg(build_config.str())
|
||||||
|
|
||||||
|
mut to_build := []Target{}
|
||||||
|
for arg in cmdline.only_non_options(args) {
|
||||||
|
to_build << targets[arg] or { errexit("Invalid target: '${arg}', abotring...") }
|
||||||
|
}
|
||||||
|
if to_build.len == 0 {
|
||||||
|
to_build = targets.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
parallel.run(to_build, |build_target| make_build(build_target) or { errexit(err.msg()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
const help_text = "
|
||||||
|
Build script options:
|
||||||
|
-targets List available targets.
|
||||||
|
-help Print this help message and exit. Aliases: help, --help.
|
||||||
|
-release Pass '-prod -cflags -static' flags to V.
|
||||||
|
|
||||||
|
Build can be configured throught environment variables:
|
||||||
|
DEBUG If set enables the verbose output as dimmed text.
|
||||||
|
BUILD_PROG_NAME Name of the compiled program. By default the name is
|
||||||
|
parsed from v.mod.
|
||||||
|
BUILD_PROG_VERSION Version of the compiled program. By default version
|
||||||
|
is parsed from v.mod.
|
||||||
|
BUILD_PROG_ENTRYPOINT The program entrypoint. Defaults to '.' (current dir).
|
||||||
|
Specify file or module which have fn main() defined.
|
||||||
|
BUILD_OUTPUT_DIR The directory where the build artifacts will be placed.
|
||||||
|
Defaults to './release'.
|
||||||
|
|
||||||
|
V-specific environment variables:
|
||||||
|
VFLAGS Set arbitrary flags for all jobs.
|
||||||
|
VJOBS Number of parallel jobs. Set it to enchanse compile speed.
|
||||||
|
".trim_indent()
|
||||||
335
make.vsh
335
make.vsh
@@ -2,305 +2,66 @@
|
|||||||
|
|
||||||
import build
|
import build
|
||||||
import crypto.sha256
|
import crypto.sha256
|
||||||
import maps
|
|
||||||
import os
|
import os
|
||||||
import term
|
import term
|
||||||
import v.vmod
|
|
||||||
|
|
||||||
const program_name = os.getenv_opt('BUILD_PROG_NAME') or { vmod_name() }
|
const output_dir = './release'
|
||||||
const program_version = os.getenv_opt('BUILD_PROG_VERSION') or { vmod_version() }
|
const docker_command = 'docker'
|
||||||
const program_entrypoint = os.getenv_opt('BUILD_PROG_ENTRYPOINT') or { '.' }
|
const docker_image = 'vlang-cross:latest-trixie'
|
||||||
const output_dir = os.abs_path(os.norm_path(os.getenv_opt('BUILD_OUTPUT_DIR') or { 'release' }))
|
const compile_command = 'env VFLAGS="-cflags -s" DEBUG=1 ./crosscompile.vsh -release'
|
||||||
const skip_targets = os.getenv('BUILD_SKIP_TARGETS')
|
|
||||||
const common_vflags = os.getenv_opt('BUILD_COMMON_VFLAGS') or { '-prod' }
|
|
||||||
const common_cflags = os.getenv_opt('BUILD_COMMON_CFLAGS') or { '-static' }
|
|
||||||
const debug = os.getenv('DEBUG')
|
|
||||||
const vexe = @VEXE
|
|
||||||
|
|
||||||
/*
|
const help_text = '
|
||||||
SETTING BUILD TARGETS
|
Options:
|
||||||
|
|
||||||
All build targets must be defined in the build_targets const below.
|
|
||||||
Each target is a V struct. Fields are:
|
|
||||||
|
|
||||||
name string
|
|
||||||
The target name. Prefer to use https://wiki.osdev.org/Target_Triplet
|
|
||||||
Note that target name will be used in output file name. For example
|
|
||||||
the 'linux-riscv64' becomes to 'myprog-1.2.3-linux-riscv64'. This is
|
|
||||||
very common naming scheme for compiled program distributions.
|
|
||||||
|
|
||||||
cc string
|
|
||||||
C Compiler to use e.g. '/usr/bin/gcc', 'clang', etc.
|
|
||||||
|
|
||||||
vflags []string
|
|
||||||
cflags []string
|
|
||||||
ldflags []string
|
|
||||||
Flags which will be passed to V compiler. See `v help build-c` for info.
|
|
||||||
|
|
||||||
file_ext string
|
|
||||||
Extension for produced binary file. Useful for Windows builds. file_ext
|
|
||||||
is concatenated to filename. For example for target named 'windows-amd64'
|
|
||||||
and '.exe' file_ext you will get 'progname-1.2.3-windows-amd64.exe'.
|
|
||||||
|
|
||||||
disabled bool
|
|
||||||
If true target will be disabled. Target building will be skipped. Also
|
|
||||||
target will not provided in tasks list in `./make.vsh -tasks` output.
|
|
||||||
|
|
||||||
common_vflags bool
|
|
||||||
If true, set additional flags listed in BUILD_COMMON_VFLAGS.
|
|
||||||
See `./make.vsh -help` for info or read help_text const below. Is true
|
|
||||||
by default.
|
|
||||||
|
|
||||||
common_cflags bool
|
|
||||||
The same as common_vflags, but for C compiler. Environment variable is
|
|
||||||
BUILD_COMMON_CFLAGS. Is true by default.
|
|
||||||
|
|
||||||
calculate_sha256 bool
|
|
||||||
If true, calculate SHA256 hashsum of produced binary and create new
|
|
||||||
artifact with the same name but with '.sha256' extension. File content
|
|
||||||
is the same as Linux `sha256sum` utility output. Is true by default.
|
|
||||||
|
|
||||||
See also Target sttruct definition in bottom of this file.
|
|
||||||
|
|
||||||
V'S SPECIAL ENVIRONMENT VARIABLES
|
|
||||||
|
|
||||||
VCROSS_COMPILER_NAME See vcross_compiler_name() in v.pref module.
|
|
||||||
VCROSS_LINKER_NAME See vcross_linker_name() in v.pref module.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const build_targets = [
|
|
||||||
Target{
|
|
||||||
name: 'linux-amd64'
|
|
||||||
cc: 'gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'linux-arm64'
|
|
||||||
cc: 'aarch64-linux-gnu-gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'linux-armhf'
|
|
||||||
cc: 'arm-linux-gnueabihf-gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'linux-ppc64le'
|
|
||||||
cc: 'powerpc64le-linux-gnu-gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'linux-s390x'
|
|
||||||
cc: 's390x-linux-gnu-gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'linux-riscv64'
|
|
||||||
cc: 'riscv64-linux-gnu-gcc'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
name: 'windows-amd64'
|
|
||||||
cc: 'x86_64-w64-mingw32-gcc'
|
|
||||||
vflags: ['-os', 'windows']
|
|
||||||
file_ext: '.exe'
|
|
||||||
},
|
|
||||||
Target{
|
|
||||||
// FreeBSD build for now is dynamically linked even if -cflags -static is passed.
|
|
||||||
// Also V forces the use of clang here (unless VCROSS_COMPILER_NAME envvar is set),
|
|
||||||
// so -cc value doesn't matter.
|
|
||||||
name: 'freebsd-amd64'
|
|
||||||
cc: 'clang'
|
|
||||||
vflags: ['-os', 'freebsd']
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const help_text = "
|
|
||||||
Build script options:
|
|
||||||
-tasks List available tasks.
|
-tasks List available tasks.
|
||||||
-help Print this help message and exit. Aliases: help, --help.
|
-help Print this help message and exit. Aliases: help, --help.
|
||||||
|
'.trim_indent()
|
||||||
|
|
||||||
Build can be configured throught environment variables:
|
if 'help' in os.args || '-help' in os.args || '--help' in os.args {
|
||||||
|
println(help_text)
|
||||||
BUILD_PROG_NAME Name of the compiled program. By default the name is
|
exit(0)
|
||||||
parsed from v.mod.
|
|
||||||
BUILD_PROG_VERSION Version of the compiled program. By default the name
|
|
||||||
is parsed from v.mod.
|
|
||||||
BUILD_PROG_ENTRYPOINT The program entrypoint. Defaults to '.' (current dir).
|
|
||||||
BUILD_OUTPUT_DIR The directory where the build artifacts will be placed.
|
|
||||||
Defaults to './release'.
|
|
||||||
BUILD_SKIP_TARGETS List of build targets to skip. Expects comma-separated
|
|
||||||
list without whitespaces e.g. 'windows-amd64,linux-armhf'
|
|
||||||
BUILD_COMMON_VFLAGS The list of V flags is common for all targets. Expects
|
|
||||||
comma-separated list. Default is '-prod,-cross'.
|
|
||||||
BUILD_COMMON_CFLAGS Same as BUILD_COMMON_VFLAGS, but passed to underlying
|
|
||||||
C compiler. Default is '-static'.
|
|
||||||
DEBUG If set enables the verbose output as dimmed text.
|
|
||||||
".trim_indent()
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if 'help' in os.args || '-help' in os.args || '--help' in os.args {
|
|
||||||
println(help_text)
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
mut context := build.context(default: 'all')
|
|
||||||
mut targets := []string{}
|
|
||||||
for build_target in build_targets {
|
|
||||||
targets << build_target.name
|
|
||||||
context.task(
|
|
||||||
name: build_target.name
|
|
||||||
help: 'Make release build for ${build_target.name} target'
|
|
||||||
run: fn [build_target] (t build.Task) ! {
|
|
||||||
make_build(build_target)!
|
|
||||||
}
|
|
||||||
should_run: fn [build_target] (t build.Task) !bool {
|
|
||||||
return is_command_present(build_target.cc)!
|
|
||||||
&& build_target.name !in skip_targets.split(',')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
context.task(
|
|
||||||
name: 'all'
|
|
||||||
help: 'Make release builds for all target systems'
|
|
||||||
depends: targets
|
|
||||||
run: |self| true
|
|
||||||
)
|
|
||||||
context.task(
|
|
||||||
name: 'clean'
|
|
||||||
help: 'Cleanup the output dir (${output_dir})'
|
|
||||||
run: |self| cleanup()!
|
|
||||||
)
|
|
||||||
context.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_build(build_target Target) ! {
|
mut context := build.context(default: 'release')
|
||||||
printdbg('Env BUILD_PROG_NAME = ${program_name}')
|
|
||||||
printdbg('Env BUILD_PROG_VERSION = ${program_version}')
|
|
||||||
printdbg('Env BUILD_PROG_ENTRYPOINT = ${program_entrypoint}')
|
|
||||||
printdbg('Env BUILD_OUTPUT_DIR = ${output_dir}')
|
|
||||||
printdbg('Env BUILD_SKIP_TARGETS = ${skip_targets.split(',')}')
|
|
||||||
printdbg('Env BUILD_COMMON_VFLAGS = ${common_vflags}')
|
|
||||||
printdbg('Env BUILD_COMMON_CFLAGS = ${common_cflags}')
|
|
||||||
|
|
||||||
os.mkdir(output_dir) or {}
|
context.task(
|
||||||
|
name: docker_image
|
||||||
|
help: 'Build Docker image for cross-compilation'
|
||||||
|
run: |self| os.system('${docker_command} build -t ${docker_image} .')
|
||||||
|
)
|
||||||
|
|
||||||
artifact := os.join_path_single(output_dir, program_name + '-' + program_version + '-' +
|
context.task(
|
||||||
build_target.name + build_target.file_ext)
|
name: 'build'
|
||||||
printdbg('Building artifact: ${artifact}')
|
help: 'Build binaries'
|
||||||
|
run: |self| os.system('${docker_command} run --rm -v .:/app ${docker_image} ${compile_command}')
|
||||||
|
should_run: |self| os.is_dir_empty(output_dir)
|
||||||
|
depends: [docker_image]
|
||||||
|
)
|
||||||
|
|
||||||
mut vargs := []string{}
|
context.task(
|
||||||
if build_target.common_vflags {
|
name: 'sha256sums'
|
||||||
for vflag in common_vflags.split(',') {
|
help: 'Calculate SHA256 sums for built binaries'
|
||||||
if vflag != '' {
|
run: |self| os.walk(output_dir, fn (file string) {
|
||||||
vargs << vflag
|
out_file := os.abs_path(file + '.sha256')
|
||||||
}
|
eprintln(term.bold('Generating: ${out_file}'))
|
||||||
}
|
data := os.read_bytes(file) or { return }
|
||||||
}
|
sum := sha256.sum(data)
|
||||||
for vflag in build_target.vflags {
|
result := '${sum.hex()} ${os.file_name(file)}\n'
|
||||||
vargs << vflag
|
os.write_file(out_file, result) or { return }
|
||||||
}
|
})
|
||||||
vargs << ['-cc', build_target.cc]
|
depends: ['build']
|
||||||
if build_target.common_cflags {
|
)
|
||||||
for cflag in common_cflags.split(',') {
|
|
||||||
if cflag != '' {
|
|
||||||
vargs << ['-cflags', cflag]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for cflag in build_target.cflags {
|
|
||||||
vargs << ['-cflags', cflag]
|
|
||||||
}
|
|
||||||
for ldflag in build_target.ldflags {
|
|
||||||
vargs << ['-ldflags', ldflag]
|
|
||||||
}
|
|
||||||
vargs << ['-o', artifact]
|
|
||||||
vargs << program_entrypoint
|
|
||||||
|
|
||||||
execute_command(vexe, vargs, env: build_target.env)!
|
context.task(
|
||||||
|
name: 'release'
|
||||||
|
help: 'Make release'
|
||||||
|
run: |self| true
|
||||||
|
depends: ['sha256sums']
|
||||||
|
)
|
||||||
|
|
||||||
if build_target.calculate_sha256 {
|
context.task(
|
||||||
sha256sum_file := artifact + '.sha256'
|
name: 'clean'
|
||||||
printdbg('Generating SHA256 sum: ${sha256sum_file}')
|
help: 'Cleanup build directory (delete all build artifacts)'
|
||||||
file_bytes := os.read_bytes(artifact)!
|
run: |self| os.rmdir_all(output_dir) or {}
|
||||||
sum := sha256.sum(file_bytes)
|
)
|
||||||
result := '${sum.hex()} ${os.file_name(artifact)}\n'
|
|
||||||
printdbg('Calculated SHA256: ${result}')
|
|
||||||
os.write_file(sha256sum_file, result)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cleanup() ! {
|
context.run()
|
||||||
printdbg('Try to delete ${output_dir} recursively...')
|
|
||||||
os.rmdir_all(output_dir) or {
|
|
||||||
if err.code() == 2 {
|
|
||||||
printdbg('${output_dir} does not exists')
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printdbg('Cleanup done')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
|
|
||||||
fn vmod_name() string {
|
|
||||||
if manifest := vmod.decode(@VMOD_FILE) {
|
|
||||||
return manifest.name
|
|
||||||
}
|
|
||||||
return 'NAMEPLACEHOLDER'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vmod_version() string {
|
|
||||||
if manifest := vmod.decode(@VMOD_FILE) {
|
|
||||||
return manifest.version
|
|
||||||
}
|
|
||||||
return 'VERSIONPLACEHOLDER'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_command_present(cmd string) !bool {
|
|
||||||
if os.exists_in_system_path(cmd) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if os.is_executable(os.abs_path(os.norm_path(cmd))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
printwarn('Command ${term.bold(cmd)} is not found')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@[params]
|
|
||||||
struct CommandOptions {
|
|
||||||
env map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_command(executable string, args []string, opts CommandOptions) ! {
|
|
||||||
path := os.find_abs_path_of_executable(executable) or { os.norm_path(executable) }
|
|
||||||
printdbg("Run '${path}' with arguments: ${args}")
|
|
||||||
mut proc := os.new_process(path)
|
|
||||||
proc.set_args(args)
|
|
||||||
proc.set_environment(maps.merge(os.environ(), opts.env))
|
|
||||||
proc.set_work_folder(os.getwd())
|
|
||||||
proc.run()
|
|
||||||
proc.wait()
|
|
||||||
if proc.status == .exited && proc.code != 0 {
|
|
||||||
return error('Command ${term.bold(path)} exited with non-zero code ${proc.code}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printdbg(s string) {
|
|
||||||
if debug !in ['', '0', 'false', 'no'] {
|
|
||||||
eprintln(term.dim(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printwarn(s string) {
|
|
||||||
eprintln(term.bright_yellow(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Target {
|
|
||||||
name string
|
|
||||||
cc string
|
|
||||||
vflags []string
|
|
||||||
cflags []string
|
|
||||||
ldflags []string
|
|
||||||
file_ext string
|
|
||||||
env map[string]string
|
|
||||||
|
|
||||||
common_vflags bool = true
|
|
||||||
common_cflags bool = true
|
|
||||||
calculate_sha256 bool = true
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user