1# SPDX-License-Identifier: BSD-2-Clause
2#
3# Copyright (c) 2024 The FreeBSD Foundation
4#
5# This software was developed by Cybermancer Infosec <bofh@FreeBSD.org>
6# under sponsorship from the FreeBSD Foundation.
7#
8# Makefile for CI testing.
9#
10# User-driven targets:
11#  ci: Run CI tests. Currently only smoke tests are supported.
12#  ci-smokeit: Currently same as ci.
13#
14# Variables affecting the build process:
15#  TARGET/TARGET_ARCH: architecture of built release (default: same as build host)
16#  KERNELCONF: kernel configuration to use
17#  USE_QEMU: Use QEMU for testing rather than bhyve
18#
19
20WORLDDIR?=	${.CURDIR}/../..
21RELEASEDIR=	${WORLDDIR}/release
22MAKECONF?=	/dev/null
23SRCCONF?=	/dev/null
24_MEMORY!=sysctl -n hw.physmem 2>/dev/null
25PARALLEL_JOBS!=sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null
26TOTAL_MEMORY!=expr ${_MEMORY} / 1073741824
27KERNCONF?=	GENERIC
28LOCALBASE?=	/usr/local
29
30.if !defined(TARGET) || empty(TARGET)
31TARGET=		${MACHINE}
32.endif
33.if !defined(TARGET_ARCH) || empty(TARGET_ARCH)
34.if ${TARGET} == ${MACHINE}
35TARGET_ARCH=	${MACHINE_ARCH}
36.else
37TARGET_ARCH=	${TARGET}
38.endif
39.endif
40IMAKE=		${MAKE} TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}
41
42.if defined(CROSS_TOOLCHAIN) || !empty(CROSS_TOOLCHAIN)
43CROSS_TOOLCHAIN_PARAM=	"CROSS_TOOLCHAIN=${CROSS_TOOLCHAIN}"
44.endif
45
46# Define OSRELEASE by using newvers.sh
47.if !defined(OSRELEASE) || empty(OSRELEASE)
48.for _V in TYPE BRANCH REVISION
49. if !defined(${_V}) || empty(${_V})
50${_V}!=	eval $$(awk '/^${_V}=/{print}' ${.CURDIR}/../../sys/conf/newvers.sh); echo $$${_V}
51. endif
52.endfor
53.for _V in ${TARGET_ARCH}
54.if !empty(TARGET:M${_V})
55OSRELEASE=	${TYPE}-${REVISION}-${BRANCH}-${TARGET}
56VOLUME_LABEL=	${REVISION:C/[.-]/_/g}_${BRANCH:C/[.-]/_/g}_${TARGET}
57.else
58OSRELEASE=	${TYPE}-${REVISION}-${BRANCH}-${TARGET}-${TARGET_ARCH}
59VOLUME_LABEL=	${REVISION:C/[.-]/_/g}_${BRANCH:C/[.-]/_/g}_${TARGET_ARCH}
60.endif
61.endfor
62.endif
63
64.if exists(${.CURDIR}/tools/ci.conf) && !defined(CICONF)
65CICONF?=	${.CURDIR}/tools/ci.conf
66.endif
67SWAPSIZE?=	1g
68VMFS?=		ufs
69FORMAT=		raw
70CIIMAGE=	ci-${OSRELEASE}-${GITREV}-${KERNCONF}.${FORMAT}
71VMSIZE?=	6g
72CITYPE?=
73TEST_VM_NAME=	ci-${OSRELEASE}-${GITREV}-${KERNCONF}
74.if ${TOTAL_MEMORY} >= 16
75VM_MEM!=expr ${TOTAL_MEMORY} / 2
76.elif ${TOTAL_MEMORY} >=4
77VM_MEM=${TOTAL_MEMORY}
78.else
79echo "Please increase the memory to at least 4GB"
80exit 0
81.endif
82VM_MEM_SIZE?=${VM_MEM}g
83TIMEOUT_MS?=5400000
84TIMEOUT=$$((${TIMEOUT_MS} / 1000))
85TIMEOUT_EXPECT=$$((${TIMEOUT} - 60))
86TIMEOUT_VM=$$((${TIMEOUT_EXPECT} - 120))
87.if exists(${.CURDIR}/Makefile.${TARGET_ARCH})
88.	include "${.CURDIR}/Makefile.${TARGET_ARCH}"
89.endif
90.if ${TARGET_ARCH} != ${MACHINE_ARCH}
91.if ( ${TARGET_ARCH} != "i386" ) || ( ${MACHINE_ARCH} != "amd64" )
92QEMUSTATIC=/usr/local/bin/qemu-${QEMU_ARCH}-static
93QEMUTGT=portinstall-qemu
94.endif
95.endif
96QEMUTGT?=
97QEMU_DEVICES?=-device virtio-blk,drive=hd0
98QEMU_EXTRA_PARAM?=
99QEMU_MACHINE?=virt
100QEMUBIN=/usr/local/bin/qemu-system-${QEMU_ARCH}
101.if ${PARALLEL_JOBS} >= ${QEMU_MAX_CPU_COUNT}
102QEMU_CPU_COUNT=${QEMU_MAX_CPU_COUNT}
103.else
104QEMU_CPU_COUNT=${PARALLEL_JOBS}
105.endif
106.if ${VM_MEM} >= ${QEMU_MAX_MEM_SIZE}
107VM_MEM_SIZE=${QEMU_MAX_MEM_SIZE}g
108.else
109VM_MEM_SIZE=${VM_MEM}g
110.endif
111KLDVMMISLOADED!=kldload -q -n vmm 2>/dev/null && echo "1" || echo "0"
112.if ${KLDVMMISLOADED} == "0"
113USE_QEMU?=1
114.endif
115KLDFILEMONISLOADED!=kldload -q -n filemon 2>/dev/null && echo "1" || echo "0"
116.if ${KLDFILEMONISLOADED} == "1"
117METAMODE?=-DWITH_META_MODE
118.endif
119
120CLEANFILES=	${CIIMAGE} ci.img
121CLEANDIRS=	ci-buildimage
122
123portinstall: portinstall-pkg portinstall-qemu portinstall-expect portinstall-${TARGET_ARCH:tl} .PHONY
124
125portinstall-pkg: .PHONY
126.if !exists(/usr/local/sbin/pkg-static)
127	env ASSUME_ALWAYS_YES=yes pkg bootstrap
128.endif
129
130portinstall-qemu: portinstall-pkg .PHONY
131.if !exists(/usr/local/bin/qemu-${TARGET_ARCH}-static)
132	env ASSUME_ALWAYS_YES=yes pkg install emulators/qemu-user-static
133.endif
134.if !exists(/usr/local/bin/qemu-system-${QEMU_ARCH})
135	env ASSUME_ALWAYS_YES=yes pkg install emulators/qemu@nox11
136.endif
137
138portinstall-expect: portinstall-pkg .PHONY
139.if !exists(/usr/local/bin/expect)
140	env ASSUME_ALWAYS_YES=yes pkg install lang/expect
141.endif
142
143beforeclean: .PHONY
144	chflags -R noschg .
145
146.include <bsd.obj.mk>
147clean: beforeclean .PHONY
148
149ci-buildworld: .PHONY
150	@echo "Building world for ${TARGET_ARCH}"
151	${IMAKE} -j${PARALLEL_JOBS} -C ${WORLDDIR} ${METAMODE} \
152		${CROSS_TOOLCHAIN_PARAM} __MAKE_CONF=${MAKECONF} SRCCONF=${SRCCONF} \
153		buildworld > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
154		(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
155
156
157ci-buildkernel: ci-buildworld-${TARGET_ARCH:tl} .PHONY
158	@echo "Building kenrel for ${TARGET_ARCH"}"
159	${IMAKE} -j${PARALLEL_JOBS} -C ${WORLDDIR} ${METAMODE} \
160		${CROSS_TOOLCHAIN_PARAM} __MAKE_CONF=${MAKECONF} \
161		SRCCONF=${SRCCONF} buildkernel > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
162		(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
163
164ci-buildimage: ${QEMUTGT} ci-buildkernel-${TARGET_ARCH:tl} .PHONY
165	@echo "Building ci image for ${TARGET_ARCH"}"
166	mkdir -p ${.OBJDIR}/${.TARGET}
167	env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} SWAPSIZE=${SWAPSIZE} \
168		QEMUSTATIC=${QEMUSTATIC} CITYPE=${CITYPE} \
169		${RELEASEDIR}/scripts/mk-vmimage.sh \
170		-C ${RELEASEDIR}/tools/vmimage.subr -d ${.OBJDIR}/${.TARGET} -F ${VMFS} \
171		-i ${.OBJDIR}/ci.img -s ${VMSIZE} -f ${FORMAT} \
172		-S ${WORLDDIR} -o ${.OBJDIR}/${CIIMAGE} -c ${CICONF} \
173		> ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
174		(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
175	touch ${.TARGET}
176
177ci-setsmokevar: .PHONY
178CITYPE=smoke
179
180ci-runtest: ci-buildimage-${TARGET_ARCH:tl} portinstall .PHONY
181.if ${MACHINE} == "amd64" && ( ${TARGET_ARCH} == "amd64" || ${TARGET_ARCH} == "i386" ) && ( !defined(USE_QEMU) || empty(USE_QEMU) )
182	/usr/sbin/bhyvectl --vm=${TEST_VM_NAME} --destroy || true
183	/usr/sbin/bhyveload -c stdio -m ${VM_MEM_SIZE} -d ${CIIMAGE} ${TEST_VM_NAME}
184	expect -c "set timeout ${TIMEOUT_EXPECT}; \
185		spawn /usr/bin/timeout -k 60 ${TIMEOUT_VM} /usr/sbin/bhyve \
186		-c ${PARALLEL_JOBS} -m ${VM_MEM_SIZE} -A -H -P \
187		-s 0:0,hostbridge \
188		-s 1:0,lpc \
189		-s 2:0,virtio-blk,${CIIMAGE} \
190		-l com1,stdio \
191		${TEST_VM_NAME}; \
192		expect { eof }"
193	/usr/sbin/bhyvectl --vm=${TEST_VM_NAME} --destroy
194.else
195	timeout -k 60 ${TIMEOUT_VM} ${QEMUBIN} \
196		-machine ${QEMU_MACHINE} \
197		-smp ${QEMU_CPU_COUNT} \
198		-m ${VM_MEM_SIZE} \
199		-nographic \
200		-no-reboot \
201		${QEMU_EXTRA_PARAM} \
202		-drive if=none,file=${CIIMAGE},format=raw,id=hd0 \
203		${QEMU_DEVICES}
204.endif
205
206ci-checktarget: .PHONY
207.if ${TARGET_ARCH} != "aarch64" && \
208	${TARGET_ARCH} != "amd64" && \
209	${TARGET_ARCH} != "armv7" && \
210	${TARGET_ARCH} != "powerpc64" && \
211	${TARGET_ARCH} != "powerpc64le" && \
212	${TARGET_ARCH} != "riscv64"
213	@false
214.ERROR:
215	@echo "Error: ${TARGET_ARCH} is not supported on ${TYPE} ${REVISION} ${BRANCH}"
216.endif
217
218ci-smokeit: ci-setsmokevar ci-checktarget .WAIT ci-runtest-${TARGET_ARCH:tl} .PHONY
219
220ci: ci-smokeit .PHONY
221
222.include "${RELEASEDIR}/Makefile.inc1"
223