| # |
| # Define WERROR=0 to disable -Werror. |
| # |
| |
| ifeq ($(strip $(V)),) |
| E = @echo |
| Q = @ |
| else |
| E = @\# |
| Q = |
| endif |
| export E Q |
| |
| include config/utilities.mak |
| include config/feature-tests.mak |
| |
| CC := $(CROSS_COMPILE)gcc |
| CFLAGS := |
| LD := $(CROSS_COMPILE)ld |
| LDFLAGS := |
| |
| FIND := find |
| CSCOPE := cscope |
| TAGS := ctags |
| INSTALL := install |
| |
| prefix = $(HOME) |
| bindir_relative = bin |
| bindir = $(prefix)/$(bindir_relative) |
| |
| DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) |
| bindir_SQ = $(subst ','\'',$(bindir)) |
| |
| PROGRAM := lkvm |
| PROGRAM_ALIAS := vm |
| |
| OBJS += builtin-balloon.o |
| OBJS += builtin-debug.o |
| OBJS += builtin-help.o |
| OBJS += builtin-list.o |
| OBJS += builtin-stat.o |
| OBJS += builtin-pause.o |
| OBJS += builtin-resume.o |
| OBJS += builtin-run.o |
| OBJS += builtin-setup.o |
| OBJS += builtin-stop.o |
| OBJS += builtin-version.o |
| OBJS += devices.o |
| OBJS += disk/core.o |
| OBJS += framebuffer.o |
| OBJS += guest_compat.o |
| OBJS += hw/rtc.o |
| OBJS += hw/serial.o |
| OBJS += ioport.o |
| OBJS += irq.o |
| OBJS += kvm-cpu.o |
| OBJS += kvm.o |
| OBJS += main.o |
| OBJS += mmio.o |
| OBJS += pci.o |
| OBJS += term.o |
| OBJS += vfio.o |
| OBJS += virtio/blk.o |
| OBJS += virtio/scsi.o |
| OBJS += virtio/console.o |
| OBJS += virtio/core.o |
| OBJS += virtio/net.o |
| OBJS += virtio/rng.o |
| OBJS += virtio/balloon.o |
| OBJS += virtio/pci.o |
| OBJS += disk/blk.o |
| OBJS += disk/qcow.o |
| OBJS += disk/raw.o |
| OBJS += ioeventfd.o |
| OBJS += net/uip/core.o |
| OBJS += net/uip/arp.o |
| OBJS += net/uip/icmp.o |
| OBJS += net/uip/ipv4.o |
| OBJS += net/uip/tcp.o |
| OBJS += net/uip/udp.o |
| OBJS += net/uip/buf.o |
| OBJS += net/uip/csum.o |
| OBJS += net/uip/dhcp.o |
| OBJS += kvm-cmd.o |
| OBJS += util/init.o |
| OBJS += util/iovec.o |
| OBJS += util/rbtree.o |
| OBJS += util/threadpool.o |
| OBJS += util/parse-options.o |
| OBJS += util/rbtree-interval.o |
| OBJS += util/strbuf.o |
| OBJS += util/read-write.o |
| OBJS += util/util.o |
| OBJS += virtio/9p.o |
| OBJS += virtio/9p-pdu.o |
| OBJS += hw/vesa.o |
| OBJS += hw/pci-shmem.o |
| OBJS += kvm-ipc.o |
| OBJS += builtin-sandbox.o |
| OBJS += virtio/mmio.o |
| OBJS += hw/i8042.o |
| |
| # Translate uname -m into ARCH string |
| ARCH ?= $(shell uname -m | sed -e s/i.86/i386/ -e s/ppc.*/powerpc/ \ |
| -e s/armv.*/arm/ -e s/aarch64.*/arm64/ -e s/mips64/mips/) |
| |
| ifeq ($(ARCH),i386) |
| ARCH := x86 |
| DEFINES += -DCONFIG_X86_32 |
| endif |
| ifeq ($(ARCH),x86_64) |
| ARCH := x86 |
| DEFINES += -DCONFIG_X86_64 |
| ARCH_PRE_INIT = x86/init.S |
| endif |
| |
| ### Arch-specific stuff |
| |
| #x86 |
| ifeq ($(ARCH),x86) |
| DEFINES += -DCONFIG_X86 |
| OBJS += x86/boot.o |
| OBJS += x86/cpuid.o |
| OBJS += x86/interrupt.o |
| OBJS += x86/ioport.o |
| OBJS += x86/irq.o |
| OBJS += x86/kvm.o |
| OBJS += x86/kvm-cpu.o |
| OBJS += x86/mptable.o |
| # Exclude BIOS object files from header dependencies. |
| OTHEROBJS += x86/bios.o |
| OTHEROBJS += x86/bios/bios-rom.o |
| ARCH_INCLUDE := x86/include |
| ARCH_HAS_FRAMEBUFFER := y |
| endif |
| # POWER/ppc: Actually only support ppc64 currently. |
| ifeq ($(ARCH), powerpc) |
| DEFINES += -DCONFIG_PPC |
| OBJS += powerpc/boot.o |
| OBJS += powerpc/ioport.o |
| OBJS += powerpc/kvm.o |
| OBJS += powerpc/cpu_info.o |
| OBJS += powerpc/kvm-cpu.o |
| OBJS += powerpc/spapr_hcall.o |
| OBJS += powerpc/spapr_rtas.o |
| OBJS += powerpc/spapr_hvcons.o |
| OBJS += powerpc/spapr_pci.o |
| OBJS += powerpc/xics.o |
| ARCH_INCLUDE := powerpc/include |
| |
| ARCH_WANT_LIBFDT := y |
| endif |
| |
| # ARM |
| OBJS_ARM_COMMON := arm/fdt.o arm/gic.o arm/ioport.o \ |
| arm/kvm.o arm/kvm-cpu.o arm/pci.o arm/timer.o \ |
| arm/pmu.o |
| HDRS_ARM_COMMON := arm/include |
| ifeq ($(ARCH), arm) |
| DEFINES += -DCONFIG_ARM |
| OBJS += $(OBJS_ARM_COMMON) |
| OBJS += arm/aarch32/arm-cpu.o |
| OBJS += arm/aarch32/kvm-cpu.o |
| ARCH_INCLUDE := $(HDRS_ARM_COMMON) |
| ARCH_INCLUDE += -Iarm/aarch32/include |
| CFLAGS += -march=armv7-a |
| |
| ARCH_WANT_LIBFDT := y |
| endif |
| |
| # ARM64 |
| ifeq ($(ARCH), arm64) |
| DEFINES += -DCONFIG_ARM64 |
| OBJS += $(OBJS_ARM_COMMON) |
| OBJS += arm/aarch64/arm-cpu.o |
| OBJS += arm/aarch64/kvm-cpu.o |
| ARCH_INCLUDE := $(HDRS_ARM_COMMON) |
| ARCH_INCLUDE += -Iarm/aarch64/include |
| |
| ARCH_WANT_LIBFDT := y |
| endif |
| |
| ifeq ($(ARCH),mips) |
| DEFINES += -DCONFIG_MIPS |
| ARCH_INCLUDE := mips/include |
| OBJS += mips/kvm.o |
| OBJS += mips/kvm-cpu.o |
| endif |
| ### |
| |
| ifeq (,$(ARCH_INCLUDE)) |
| $(error This architecture ($(ARCH)) is not supported in kvmtool) |
| endif |
| |
| ### |
| |
| # Detect optional features. |
| # On a given system, some libs may link statically, some may not; so, check |
| # both and only build those that link! |
| |
| ifeq ($(call try-build,$(SOURCE_STRLCPY),$(CFLAGS),$(LDFLAGS)),y) |
| CFLAGS_DYNOPT += -DHAVE_STRLCPY |
| CFLAGS_STATOPT += -DHAVE_STRLCPY |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_BFD),$(CFLAGS),$(LDFLAGS) -lbfd -static),y) |
| CFLAGS_STATOPT += -DCONFIG_HAS_BFD |
| OBJS_STATOPT += symbol.o |
| LIBS_STATOPT += -lbfd |
| else |
| NOTFOUND += bfd |
| endif |
| |
| ifeq (y,$(ARCH_HAS_FRAMEBUFFER)) |
| CFLAGS_GTK3 := $(shell pkg-config --cflags gtk+-3.0 2>/dev/null) |
| LDFLAGS_GTK3 := $(shell pkg-config --libs gtk+-3.0 2>/dev/null) |
| ifeq ($(call try-build,$(SOURCE_GTK3),$(CFLAGS) $(CFLAGS_GTK3),$(LDFLAGS) $(LDFLAGS_GTK3)),y) |
| OBJS_DYNOPT += ui/gtk3.o |
| CFLAGS_DYNOPT += -DCONFIG_HAS_GTK3 $(CFLAGS_GTK3) |
| LIBS_DYNOPT += $(LDFLAGS_GTK3) |
| else |
| NOTFOUND += GTK3 |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_VNCSERVER),$(CFLAGS),$(LDFLAGS) -lvncserver),y) |
| OBJS_DYNOPT += ui/vnc.o |
| CFLAGS_DYNOPT += -DCONFIG_HAS_VNCSERVER |
| LIBS_DYNOPT += -lvncserver |
| else |
| NOTFOUND += vncserver |
| endif |
| ifeq ($(call try-build,$(SOURCE_VNCSERVER),$(CFLAGS),$(LDFLAGS) -lvncserver -static),y) |
| OBJS_STATOPT += ui/vnc.o |
| CFLAGS_STATOPT += -DCONFIG_HAS_VNCSERVER |
| LIBS_STATOPT += -lvncserver |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_SDL),$(CFLAGS),$(LDFLAGS) -lSDL),y) |
| OBJS_DYNOPT += ui/sdl.o |
| CFLAGS_DYNOPT += -DCONFIG_HAS_SDL |
| LIBS_DYNOPT += -lSDL |
| else |
| NOTFOUND += SDL |
| endif |
| ifeq ($(call try-build,$(SOURCE_SDL),$(CFLAGS),$(LDFLAGS) -lSDL -static), y) |
| OBJS_STATOPT += ui/sdl.o |
| CFLAGS_STATOPT += -DCONFIG_HAS_SDL |
| LIBS_STATOPT += -lSDL |
| endif |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_ZLIB),$(CFLAGS),$(LDFLAGS) -lz),y) |
| CFLAGS_DYNOPT += -DCONFIG_HAS_ZLIB |
| LIBS_DYNOPT += -lz |
| else |
| NOTFOUND += zlib |
| endif |
| ifeq ($(call try-build,$(SOURCE_ZLIB),$(CFLAGS),$(LDFLAGS) -lz -static),y) |
| CFLAGS_STATOPT += -DCONFIG_HAS_ZLIB |
| LIBS_STATOPT += -lz |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_AIO),$(CFLAGS),$(LDFLAGS) -laio),y) |
| CFLAGS_DYNOPT += -DCONFIG_HAS_AIO |
| LIBS_DYNOPT += -laio |
| else |
| NOTFOUND += aio |
| endif |
| ifeq ($(call try-build,$(SOURCE_AIO),$(CFLAGS),$(LDFLAGS) -laio -static),y) |
| CFLAGS_STATOPT += -DCONFIG_HAS_AIO |
| LIBS_STATOPT += -laio |
| endif |
| |
| ifeq ($(LTO),1) |
| FLAGS_LTO := -flto |
| ifeq ($(call try-build,$(SOURCE_HELLO),$(CFLAGS),$(LDFLAGS) $(FLAGS_LTO)),y) |
| CFLAGS += $(FLAGS_LTO) |
| endif |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_STATIC),$(CFLAGS),$(LDFLAGS) -static),y) |
| CFLAGS += -DCONFIG_GUEST_INIT |
| GUEST_INIT := guest/init |
| GUEST_OBJS = guest/guest_init.o |
| ifeq ($(ARCH_PRE_INIT),) |
| GUEST_INIT_FLAGS += -static $(PIE_FLAGS) |
| else |
| CFLAGS += -DCONFIG_GUEST_PRE_INIT |
| GUEST_INIT_FLAGS += -DCONFIG_GUEST_PRE_INIT |
| GUEST_PRE_INIT := guest/pre_init |
| GUEST_OBJS += guest/guest_pre_init.o |
| endif |
| else |
| $(warning No static libc found. Skipping guest init) |
| NOTFOUND += static-libc |
| endif |
| |
| ifeq (y,$(ARCH_WANT_LIBFDT)) |
| ifneq ($(call try-build,$(SOURCE_LIBFDT),$(CFLAGS),-lfdt),y) |
| $(error No libfdt found. Please install libfdt-dev package) |
| else |
| CFLAGS_DYNOPT += -DCONFIG_HAS_LIBFDT |
| CFLAGS_STATOPT += -DCONFIG_HAS_LIBFDT |
| LIBS_DYNOPT += -lfdt |
| LIBS_STATOPT += -lfdt |
| endif |
| endif |
| |
| ifeq ($(call try-build,$(SOURCE_HELLO),$(CFLAGS),-no-pie),y) |
| PIE_FLAGS += -no-pie |
| endif |
| |
| ifneq ($(NOTFOUND),) |
| $(warning Skipping optional libraries: $(NOTFOUND)) |
| endif |
| |
| ### |
| |
| LIBS += -lrt |
| LIBS += -lpthread |
| LIBS += -lutil |
| |
| |
| comma = , |
| |
| # The dependency file for the current target |
| depfile = $(subst $(comma),_,$(dir $@).$(notdir $@).d) |
| |
| DEPS := $(foreach obj,$(OBJS),\ |
| $(subst $(comma),_,$(dir $(obj)).$(notdir $(obj)).d)) |
| |
| DEFINES += -D_FILE_OFFSET_BITS=64 |
| DEFINES += -D_GNU_SOURCE |
| DEFINES += -DKVMTOOLS_VERSION='"$(KVMTOOLS_VERSION)"' |
| DEFINES += -DBUILD_ARCH='"$(ARCH)"' |
| |
| KVM_INCLUDE := include |
| CFLAGS += $(CPPFLAGS) $(DEFINES) -I$(KVM_INCLUDE) -I$(ARCH_INCLUDE) -O2 -fno-strict-aliasing -g |
| |
| WARNINGS += -Wall |
| WARNINGS += -Wformat=2 |
| WARNINGS += -Winit-self |
| WARNINGS += -Wmissing-declarations |
| WARNINGS += -Wmissing-prototypes |
| WARNINGS += -Wnested-externs |
| WARNINGS += -Wno-system-headers |
| WARNINGS += -Wold-style-definition |
| WARNINGS += -Wredundant-decls |
| WARNINGS += -Wsign-compare |
| WARNINGS += -Wstrict-prototypes |
| WARNINGS += -Wundef |
| WARNINGS += -Wvolatile-register-var |
| WARNINGS += -Wwrite-strings |
| WARNINGS += -Wno-format-nonliteral |
| |
| CFLAGS += $(WARNINGS) |
| |
| ifneq ($(WERROR),0) |
| CFLAGS += -Werror |
| endif |
| |
| all: $(PROGRAM) $(PROGRAM_ALIAS) |
| |
| # CFLAGS used when building objects |
| # This is intentionally not assigned using := |
| c_flags = -Wp,-MD,$(depfile) $(CFLAGS) |
| |
| # When building -static all objects are built with appropriate flags, which |
| # may differ between static & dynamic .o. The objects are separated into |
| # .o and .static.o. See the %.o: %.c rules below. |
| # |
| # $(OTHEROBJS) are things that do not get substituted like this. |
| # |
| STATIC_OBJS = $(patsubst %.o,%.static.o,$(OBJS) $(OBJS_STATOPT)) |
| |
| STATIC_DEPS := $(foreach obj,$(STATIC_OBJS),\ |
| $(subst $(comma),_,$(dir $(obj)).$(notdir $(obj)).d)) |
| |
| $(PROGRAM)-static: $(STATIC_OBJS) $(OTHEROBJS) $(GUEST_OBJS) |
| $(E) " LINK " $@ |
| $(Q) $(CC) -static $(CFLAGS) $(STATIC_OBJS) $(OTHEROBJS) $(GUEST_OBJS) $(LDFLAGS) $(LIBS) $(LIBS_STATOPT) -o $@ |
| |
| $(PROGRAM): $(OBJS) $(OBJS_DYNOPT) $(OTHEROBJS) $(GUEST_OBJS) |
| $(E) " LINK " $@ |
| $(Q) $(CC) $(CFLAGS) $(OBJS) $(OBJS_DYNOPT) $(OTHEROBJS) $(GUEST_OBJS) $(LDFLAGS) $(LIBS) $(LIBS_DYNOPT) -o $@ |
| |
| $(PROGRAM_ALIAS): $(PROGRAM) |
| $(E) " LN " $@ |
| $(Q) ln -f $(PROGRAM) $@ |
| |
| ifneq ($(ARCH_PRE_INIT),) |
| $(GUEST_PRE_INIT): $(ARCH_PRE_INIT) |
| $(E) " COMPILE " $@ |
| $(Q) $(CC) -s $(PIE_FLAGS) -nostdlib $< -o $@ |
| |
| guest/guest_pre_init.c: $(GUEST_PRE_INIT) |
| $(E) " CONVERT " $@ |
| $(Q) $(call binary-to-C,$<,pre_init_binary,$@) |
| endif |
| |
| $(GUEST_INIT): guest/init.c |
| $(E) " COMPILE " $@ |
| $(Q) $(CC) $(GUEST_INIT_FLAGS) $< -o $@ |
| |
| guest/guest_init.c: $(GUEST_INIT) |
| $(E) " CONVERT " $@ |
| $(Q) $(call binary-to-C,$<,init_binary,$@) |
| |
| %.s: %.c |
| $(Q) $(CC) -o $@ -S $(CFLAGS) -fverbose-asm $< |
| |
| $(OBJS): |
| |
| util/rbtree.static.o util/rbtree.o: util/rbtree.c |
| ifeq ($(C),1) |
| $(E) " CHECK " $@ |
| $(Q) $(CHECK) -c $(CFLAGS) $< -o $@ |
| endif |
| $(E) " CC " $@ |
| $(Q) $(CC) -c $(c_flags) $< -o $@ |
| |
| %.static.o: %.c |
| ifeq ($(C),1) |
| $(E) " CHECK " $@ |
| $(Q) $(CHECK) -c $(CFLAGS) $(CFLAGS_STATOPT) $< -o $@ |
| endif |
| $(E) " CC " $@ |
| $(Q) $(CC) -c $(c_flags) $(CFLAGS_STATOPT) $< -o $@ |
| |
| %.o: %.c |
| ifeq ($(C),1) |
| $(E) " CHECK " $@ |
| $(Q) $(CHECK) -c $(CFLAGS) $(CFLAGS_DYNOPT) $< -o $@ |
| endif |
| $(E) " CC " $@ |
| $(Q) $(CC) -c $(c_flags) $(CFLAGS_DYNOPT) $< -o $@ |
| |
| |
| # |
| # BIOS assembly weirdness |
| # |
| BIOS_CFLAGS += -m32 |
| BIOS_CFLAGS += -march=i386 |
| BIOS_CFLAGS += -mregparm=3 |
| |
| BIOS_CFLAGS += -fno-stack-protector |
| BIOS_CFLAGS += -fno-pic |
| |
| x86/bios.o: x86/bios/bios.bin x86/bios/bios-rom.h |
| |
| x86/bios/bios.bin.elf: x86/bios/entry.S x86/bios/e820.c x86/bios/int10.c x86/bios/int15.c x86/bios/rom.ld.S |
| $(E) " CC x86/bios/memcpy.o" |
| $(Q) $(CC) -include code16gcc.h $(CFLAGS) $(BIOS_CFLAGS) -c x86/bios/memcpy.c -o x86/bios/memcpy.o |
| $(E) " CC x86/bios/e820.o" |
| $(Q) $(CC) -include code16gcc.h $(CFLAGS) $(BIOS_CFLAGS) -c x86/bios/e820.c -o x86/bios/e820.o |
| $(E) " CC x86/bios/int10.o" |
| $(Q) $(CC) -include code16gcc.h $(CFLAGS) $(BIOS_CFLAGS) -c x86/bios/int10.c -o x86/bios/int10.o |
| $(E) " CC x86/bios/int15.o" |
| $(Q) $(CC) -include code16gcc.h $(CFLAGS) $(BIOS_CFLAGS) -c x86/bios/int15.c -o x86/bios/int15.o |
| $(E) " CC x86/bios/entry.o" |
| $(Q) $(CC) $(CFLAGS) $(BIOS_CFLAGS) -c x86/bios/entry.S -o x86/bios/entry.o |
| $(E) " LD " $@ |
| $(Q) $(LD) -T x86/bios/rom.ld.S -o x86/bios/bios.bin.elf x86/bios/memcpy.o x86/bios/entry.o x86/bios/e820.o x86/bios/int10.o x86/bios/int15.o |
| |
| x86/bios/bios.bin: x86/bios/bios.bin.elf |
| $(E) " OBJCOPY " $@ |
| $(Q) objcopy -O binary -j .text x86/bios/bios.bin.elf x86/bios/bios.bin |
| |
| x86/bios/bios-rom.o: x86/bios/bios-rom.S x86/bios/bios.bin x86/bios/bios-rom.h |
| $(E) " CC " $@ |
| $(Q) $(CC) -c $(CFLAGS) x86/bios/bios-rom.S -o x86/bios/bios-rom.o |
| |
| x86/bios/bios-rom.h: x86/bios/bios.bin.elf |
| $(E) " NM " $@ |
| $(Q) cd x86/bios && sh gen-offsets.sh > bios-rom.h && cd .. |
| |
| check: all |
| $(MAKE) -C tests |
| ./$(PROGRAM) run tests/pit/tick.bin |
| ./$(PROGRAM) run -d tests/boot/boot_test.iso -p "init=init" |
| .PHONY: check |
| |
| install: all |
| $(E) " INSTALL" |
| $(Q) $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' |
| $(Q) $(INSTALL) $(PROGRAM) '$(DESTDIR_SQ)$(bindir_SQ)' |
| .PHONY: install |
| |
| clean: |
| $(E) " CLEAN" |
| $(Q) rm -f x86/bios/*.bin |
| $(Q) rm -f x86/bios/*.elf |
| $(Q) rm -f x86/bios/*.o |
| $(Q) rm -f x86/bios/bios-rom.h |
| $(Q) rm -f tests/boot/boot_test.iso |
| $(Q) rm -rf tests/boot/rootfs/ |
| $(Q) rm -f $(DEPS) $(STATIC_DEPS) $(OBJS) $(OTHEROBJS) $(OBJS_DYNOPT) $(STATIC_OBJS) $(PROGRAM) $(PROGRAM_ALIAS) $(PROGRAM)-static $(GUEST_INIT) $(GUEST_PRE_INIT) $(GUEST_OBJS) |
| $(Q) rm -f guest/guest_init.c guest/guest_pre_init.c |
| $(Q) rm -f cscope.* |
| $(Q) rm -f tags |
| $(Q) rm -f TAGS |
| $(Q) rm -f KVMTOOLS-VERSION-FILE |
| .PHONY: clean |
| |
| KVM_DEV ?= /dev/kvm |
| |
| $(KVM_DEV): |
| $(E) " MKNOD " $@ |
| $(Q) mknod $@ char 10 232 |
| |
| devices: $(KVM_DEV) |
| .PHONY: devices |
| |
| TAGS: |
| $(E) " GEN" $@ |
| $(Q) $(RM) -f TAGS |
| $(Q) $(FIND) . -name '*.[hcS]' -print | xargs etags -a |
| .PHONY: TAGS |
| |
| tags: |
| $(E) " GEN" $@ |
| $(Q) $(RM) -f tags |
| $(Q) $(FIND) . -name '*.[hcS]' -print | xargs ctags -a |
| .PHONY: tags |
| |
| cscope: |
| $(E) " GEN" $@ |
| $(Q) $(FIND) . -name '*.[hcS]' -print > cscope.files |
| $(Q) $(CSCOPE) -bkqu |
| .PHONY: cscope |
| |
| # |
| # Escape redundant work on cleaning up |
| ifneq ($(MAKECMDGOALS),clean) |
| -include $(DEPS) |
| |
| KVMTOOLS-VERSION-FILE: |
| @$(SHELL_PATH) util/KVMTOOLS-VERSION-GEN $(OUTPUT) |
| -include $(OUTPUT)KVMTOOLS-VERSION-FILE |
| endif |