Makefile - Guía rápida

Compilar los archivos de código fuente puede resultar agotador, especialmente cuando tiene que incluir varios archivos fuente y escribir el comando de compilación cada vez que necesita compilar. Los Makefiles son la solución para simplificar esta tarea.

Makefiles son archivos de formato especial que ayudan a construir y administrar los proyectos automáticamente.

Por ejemplo, supongamos que tenemos los siguientes archivos fuente.

  • main.cpp
  • hello.cpp
  • factorial.cpp
  • functions.h

main.cpp

El siguiente es el código para el archivo fuente main.cpp:

#include <iostream>

using namespace std;

#include "functions.h"

int main(){
   print_hello();
   cout << endl;
   cout << "The factorial of 5 is " << factorial(5) << endl;
   return 0;
}

hello.cpp

El código que se proporciona a continuación es para el archivo fuente hello.cpp:

#include <iostream>

using namespace std;

#include "functions.h"

void print_hello(){
   cout << "Hello World!";
}

factorial.cpp

El código para factorial.cpp se da a continuación:

#include "functions.h"

int factorial(int n){
   
   if(n!=1){
      return(n * factorial(n-1));
   } else return 1;
}

functions.h

El siguiente es el código para fnctions.h -

void print_hello();
int factorial(int n);

La forma trivial de compilar los archivos y obtener un ejecutable es ejecutando el comando:

gcc  main.cpp hello.cpp factorial.cpp -o hello

Este comando genera un binario de saludo . En este ejemplo, solo tenemos cuatro archivos y conocemos la secuencia de las llamadas a la función. Por lo tanto, es posible escribir el comando anterior y preparar un binario final.

Sin embargo, para un proyecto grande donde tenemos miles de archivos de código fuente, resulta difícil mantener las compilaciones binarias.

los makeEl comando le permite administrar grandes programas o grupos de programas. A medida que comienza a escribir programas grandes, nota que volver a compilar programas grandes lleva más tiempo que volver a compilar programas cortos. Además, observa que normalmente solo trabaja en una pequeña sección del programa (como una única función) y gran parte del programa restante no se modifica.

En la siguiente sección, vemos cómo preparar un archivo MAKE para nuestro proyecto.

los makeEl programa le permite utilizar macros, que son similares a las variables. Las macros se definen en un Makefile como = pares. A continuación se muestra un ejemplo:

MACROS  = -me
PSROFF  = groff -Tps
DITROFF = groff -Tdvi
CFLAGS  = -O -systype bsd43
LIBS    = "-lncurses -lm -lsdl"
MYFACE  = ":*)"

Macros especiales

Antes de emitir cualquier comando en un conjunto de reglas de destino, hay ciertas macros especiales predefinidas:

  • $ @ es el nombre del archivo que se creará.

  • PS son los nombres de los dependientes cambiados.

Por ejemplo, podríamos usar una regla de la siguiente manera:

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) $? $(LDFLAGS) -o [email protected]

Alternatively:

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o [email protected]

En este ejemplo, $ @ representa hola y $? o $ @. cpp recoge todos los archivos fuente modificados.

Hay dos macros especiales más utilizadas en las reglas implícitas. Ellos son -

  • $ <el nombre del archivo relacionado que provocó la acción.

  • $ * el prefijo compartido por los archivos de destino y dependientes.

La regla implícita común es para la construcción de archivos .o (objeto) a partir de .cpp (archivos fuente).

.cpp.o:
   $(CC) $(CFLAGS) -c $<

Alternatively:

.cpp.o:
   $(CC) $(CFLAGS) -c $*.c

Macros convencionales

Hay varias macros predeterminadas. Puede verlos escribiendo "make -p" para imprimir los valores predeterminados. La mayoría son bastante obvias por las reglas en las que se utilizan.

Estas variables predefinidas, es decir, las macros utilizadas en reglas implícitas, se dividen en dos clases. Son los siguientes:

  • Macros que son nombres de programas (como CC)

  • Macros que contienen argumentos de los programas (como CFLAGS).

A continuación se muestra una tabla de algunas de las variables comunes que se usan como nombres de programas en las reglas integradas de los archivos MAKE:

No Señor Variables y descripción
1

AR

Programa de mantenimiento de archivos; el predeterminado es 'ar'.

2

AS

Programa para compilar archivos de ensamblaje; el valor predeterminado es "como".

3

CC

Programa para compilar programas en C; el predeterminado es 'cc'.

4

CO

Programa para verificar archivos de RCS; el valor predeterminado es 'co'.

5

CXX

Programa para compilar programas C ++; el predeterminado es `g ++ '.

6

CPP

Programa para ejecutar el preprocesador C, con resultados para salida estándar; el valor predeterminado es '$ (CC) -E'.

7

FC

Programa para compilar o preprocesar programas Fortran y Ratfor; el predeterminado es `f77 '.

8

GET

Programa para extraer un archivo de SCCS; el valor predeterminado es "get".

9

LEX

Programa a utilizar para convertir las gramáticas Lex en código fuente; el valor predeterminado es `lex '.

10

YACC

Programa a utilizar para convertir las gramáticas Yacc en código fuente; el predeterminado es `yacc '.

11

LINT

Programa a utilizar para ejecutar lint en el código fuente; el valor predeterminado es "pelusa".

12

M2C

Programa a utilizar para compilar código fuente Modula-2; el predeterminado es 'm2c'.

13

PC

Programa para compilar programas Pascal; el predeterminado es `pc '.

14

MAKEINFO

Programa para convertir un archivo fuente Texinfo en un archivo Info; el predeterminado es `makeinfo '.

15

TEX

Programa para crear archivos TeX dvi desde la fuente TeX; por defecto es `tex '.

dieciséis

TEXI2DVI

Programa para crear archivos TeX dvi desde la fuente Texinfo; el predeterminado es `texi2dvi '.

17

WEAVE

Programa para traducir Web a TeX; el valor predeterminado es "tejido".

18

CWEAVE

Programa para traducir C Web a TeX; el valor predeterminado es `cweave '.

19

TANGLE

Programa para traducir Web a Pascal; el valor predeterminado es "enredo".

20

CTANGLE

Programa para traducir C Web a C; el valor predeterminado es "ctangle".

21

RM

Comando para eliminar un archivo; el predeterminado es `rm -f '.

Aquí hay una tabla de variables cuyos valores son argumentos adicionales para los programas anteriores. Los valores predeterminados para todos estos son la cadena vacía, a menos que se indique lo contrario.

No Señor. Variables y descripción
1

ARFLAGS

Banderas para dar el programa de mantenimiento de archivos; por defecto es `rv '.

2

ASFLAGS

Indicadores adicionales para dar al ensamblador cuando se invoca explícitamente en un archivo '.s' o '.S'.

3

CFLAGS

Indicadores adicionales para entregar al compilador de C.

4

CXXFLAGS

Indicadores adicionales para entregar al compilador de C.

5

COFLAGS

Banderas extra para dar al programa RCS co.

6

CPPFLAGS

Indicadores adicionales para entregar al preprocesador de C y a los programas que lo usan (como los compiladores de C y Fortran).

7

FFLAGS

Indicadores adicionales para entregar al compilador de Fortran.

8

GFLAGS

Banderas adicionales para entregar al programa de obtención de SCCS.

9

LDFLAGS

Indicadores adicionales para dar a los compiladores cuando se supone que deben invocar el enlazador, `ld '.

10

LFLAGS

Banderas extra para regalar a Lex.

11

YFLAGS

Banderas extra para regalar a Yacc.

12

PFLAGS

Indicadores adicionales para entregar al compilador de Pascal.

13

RFLAGS

Indicadores adicionales para entregar al compilador Fortran para programas Ratfor.

14

LINTFLAGS

Banderas extra para dar a la pelusa.

NOTE - Puede cancelar todas las variables utilizadas por las reglas implícitas con la opción '-R' o '--no-builtin-variables'.

También puede definir macros en la línea de comando como se muestra a continuación:

make CPP = /home/courses/cop4530/spring02

Es muy común que un binario final dependa de varios códigos fuente y archivos de encabezado fuente. Las dependencias son importantes porque permitenmakeConocido sobre el origen de cualquier objetivo. Considere el siguiente ejemplo:

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

Aquí te decimos makeese saludo depende de los archivos main.o, factorial.o y hello.o. Por lo tanto, siempre que haya un cambio en cualquiera de estos archivos de objeto,make tomará acción.

Al mismo tiempo, necesitamos decirle al makecómo preparar archivos .o. Por lo tanto, necesitamos definir esas dependencias también de la siguiente manera:

main.o: main.cpp functions.h
   $(CC) -c main.cpp

factorial.o: factorial.cpp functions.h
   $(CC) -c factorial.cpp

hello.o: hello.cpp functions.h
   $(CC) -c hello.cpp

Ahora aprenderemos las reglas de Makefile.

La sintaxis general de una regla de destino Makefile es:

target [target...] : [dependent ....]
[ command ...]

En el código anterior, los argumentos entre paréntesis son opcionales y la elipsis significa uno o más. Aquí, tenga en cuenta que se requiere la pestaña para preceder cada comando.

A continuación se ofrece un ejemplo sencillo en el que se define una regla para hacer que su objetivo salude desde otros tres archivos.

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

NOTE - En este ejemplo, tendría que dar reglas para crear todos los archivos objeto a partir de los archivos fuente.

La semántica es muy sencilla. Cuando dices "hacer objetivo", elmakeencuentra la regla de destino que se aplica; y, si alguno de los dependientes es más nuevo que el objetivo,makeejecuta los comandos uno a la vez (después de la sustitución de macros). Si hay que hacer dependientes, eso sucede primero (por lo que tiene una recursión).

Maketermina si algún comando devuelve un estado de falla. En tal caso, se mostrará la siguiente regla:

clean:
   -rm *.o *~ core paper

Makeignora el estado devuelto en las líneas de comando que comienzan con un guión. Por ejemplo, ¿a quién le importa si no hay un archivo principal?

Makerepite los comandos, después de la sustitución de macros para mostrarle lo que está sucediendo. A veces es posible que desee desactivar eso. Por ejemplo

install:
   @echo You must be root to install

La gente ha llegado a esperar ciertos objetivos en Makefiles. Siempre debes navegar primero. Sin embargo, es razonable esperar que se encuentren todos los objetivos (o simplemente hacer), instalar y limpiar.

  • make all - Compila todo para que puedas hacer pruebas locales antes de instalar aplicaciones.

  • make install - Instala aplicaciones en los lugares adecuados.

  • make clean - Limpia aplicaciones, elimina los ejecutables, cualquier archivo temporal, archivo objeto, etc.

Reglas implícitas de Makefile

El comando es uno que debería funcionar en todos los casos en los que construimos un ejecutable x a partir del código fuente x.cpp. Esto puede establecerse como una regla implícita:

.cpp:
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o [email protected]

Esta regla implícita dice cómo hacer x con xc - ejecutar cc en xc y llamar a la salida x. La regla está implícita porque no se menciona ningún objetivo en particular. Se puede utilizar en todos los casos.

Otra regla implícita común es la construcción de archivos .o (objeto) a partir de .cpp (archivos fuente).

.cpp.o:
   $(CC) $(CFLAGS) -c $<

alternatively

.cpp.o:
   $(CC) $(CFLAGS) -c $*.cpp

Makepuede crear automáticamente un archivo ao, usando cc -c en el archivo .c correspondiente. Estas reglas están integradasmake, y puede aprovechar esta ventaja para acortar su Makefile. Si indica solo los archivos .h en la línea de dependencia del Makefile del que depende el destino actual,makesabrá que el archivo .c correspondiente ya es necesario. No es necesario que incluya el comando para el compilador.

Esto reduce aún más el Makefile, como se muestra a continuación:

OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
   cc $(OBJECTS) -o hello
hellp.o: functions.h

main.o: functions.h 
factorial.o: functions.h

Makeutiliza un objetivo especial, llamado .SUFFIXES para permitirle definir sus propios sufijos. Por ejemplo, consulte la línea de dependencia que se muestra a continuación:

.SUFFIXES: .foo .bar

Informa make que utilizará estos sufijos especiales para crear sus propias reglas.

Similar a como makeya sabe cómo hacer un archivo .o a partir de un archivo .c , puede definir reglas de la siguiente manera:

.foo.bar:
   tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' < $< > [email protected]
.c.o:
   $(CC) $(CFLAGS) -c $<

La primera regla le permite crear un archivo .bar a partir de un archivo .foo . Básicamente, codifica el archivo. La segunda regla es la regla predeterminada utilizada pormakepara crear un archivo .o a partir de un archivo .c .

Existen numerosas directivas disponibles en diversas formas. losmakeEs posible que el programa de su sistema no admita todas las directivas. Así que compruebe si sumake apoya las directivas que estamos explicando aquí. GNU make apoya estas directivas.

Directivas condicionales

Las directivas condicionales son:

  • los ifeqLa directiva comienza el condicional y especifica la condición. Contiene dos argumentos, separados por una coma y entre paréntesis. La sustitución de variables se realiza en ambos argumentos y luego se comparan. Las líneas del archivo MAKE que siguen al ifeq se obedecen si los dos argumentos coinciden; de lo contrario, se ignoran.

  • los ifneqLa directiva comienza el condicional y especifica la condición. Contiene dos argumentos, separados por una coma y entre paréntesis. La sustitución de variables se realiza en ambos argumentos y luego se comparan. Las líneas del archivo MAKE que siguen a ifneq se obedecen si los dos argumentos no coinciden; de lo contrario, se ignoran.

  • los ifdefLa directiva comienza el condicional y especifica la condición. Contiene un solo argumento. Si el argumento dado es verdadero, la condición se vuelve verdadera.

  • los ifndefLa directiva comienza el condicional y especifica la condición. Contiene un solo argumento. Si el argumento dado es falso, la condición se convierte en verdadera.

  • los elseLa directiva hace que se obedezcan las siguientes líneas si el condicional anterior falló. En el ejemplo anterior, esto significa que el segundo comando de vinculación alternativo se usa siempre que no se usa la primera alternativa. Es opcional tener un else en un condicional.

  • los endifLa directiva finaliza el condicional. Todo condicional debe terminar con un endif.

Sintaxis de las directivas condicionales

La sintaxis de un condicional simple sin más es la siguiente:

conditional-directive
   text-if-true
endif

El texto si es verdadero puede ser cualquier línea de texto, para ser considerado como parte del archivo MAKE si la condición es verdadera. Si la condición es falsa, no se usa texto en su lugar.

La sintaxis de un condicional complejo es la siguiente:

conditional-directive
   text-if-true
else
   text-if-false
endif

Si la condición es verdadera, se usa text-if-true; de lo contrario, se utiliza text-if-false. El texto si es falso puede ser cualquier número de líneas de texto.

La sintaxis de la directiva condicional es la misma si el condicional es simple o complejo. Hay cuatro directivas diferentes que prueban varias condiciones. Son como se dan -

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"

Las directivas opuestas de las condiciones anteriores son las siguientes:

ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2"

Ejemplo de directivas condicionales

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
   $(CC) -o foo $(objects) $(libs_for_gcc)
else
   $(CC) -o foo $(objects) $(normal_libs)
endif

La directiva de inclusión

los include directive permite makepara suspender la lectura del archivo MAKE actual y leer uno o más archivos MAKE antes de continuar. La directiva es una línea en el archivo MAKE que se ve a continuación:

include filenames...

Los nombres de archivo pueden contener patrones de nombres de archivos de shell. Se permiten e ignoran espacios adicionales al principio de la línea, pero no se permite una tabulación. Por ejemplo, si tiene tres archivos `.mk ', a saber,` a.mk', `b.mk 'y` c.mk', y $ (bar), entonces se expande a bish bash, y luego lo siguiente expresión.

include foo *.mk $(bar)

is equivalent to:

include foo a.mk b.mk c.mk bish bash

Cuando el makeprocesa una directiva de inclusión, suspende la lectura del archivo MAKE y lee de cada archivo listado a su vez. Cuando eso termine,make reanuda la lectura del archivo MAKE en el que aparece la directiva.

La Directiva de anulación

Si se ha establecido una variable con un argumento de comando, se ignoran las asignaciones ordinarias en el archivo MAKE. Si desea establecer la variable en el archivo MAKE a pesar de que se estableció con un argumento de comando, puede usar una directiva de anulación, que es una línea que se ve a continuación:

override variable = value

or

override variable := value

los makeEl programa es una utilidad inteligente y funciona en función de los cambios que realice en sus archivos fuente. Si tiene cuatro archivos main.cpp, hello.cpp, factorial.cpp y functions.h, entonces todos los archivos restantes dependen de functions.h, y main.cpp depende de hello.cpp y factorial.cpp. Por lo tanto, si realiza cambios en functions.h, entonces elmakevuelve a compilar todos los archivos de origen para generar nuevos archivos de objeto. Sin embargo, si realiza algún cambio en main.cpp, ya que no depende de ningún otro archivo, solo se vuelve a compilar el archivo main.cpp, y help.cpp y factorial.cpp no.

Al compilar un archivo, el makecomprueba su archivo de objeto y compara las marcas de tiempo. Si el archivo de origen tiene una marca de tiempo más reciente que el archivo de objeto, genera un nuevo archivo de objeto asumiendo que el archivo de origen ha sido modificado.

Evitar la recompilación

Puede haber un proyecto que consta de miles de archivos. A veces, es posible que haya cambiado un archivo de origen, pero es posible que no desee volver a compilar todos los archivos que dependen de él. Por ejemplo, suponga que agrega una macro o una declaración a un archivo de encabezado, del cual dependen los otros archivos. Siendo conservadormake asume que cualquier cambio en el archivo de encabezado requiere la recompilación de todos los archivos dependientes, pero usted sabe que no necesitan recompilación y prefiere no perder el tiempo esperando a que se compilen.

Si anticipa el problema antes de cambiar el archivo de encabezado, puede usar la bandera `-t '. Esta bandera dicemakeno para ejecutar los comandos en las reglas, sino para marcar el objetivo como actualizado cambiando su fecha de última modificación. Debe seguir este procedimiento:

  • Use el comando 'make' para recompilar los archivos fuente que realmente necesitan recompilación.

  • Realice los cambios en los archivos de encabezado.

  • Utilice el comando 'make -t' para marcar todos los archivos objeto como actualizados. La próxima vez que ejecute make, los cambios en los archivos de encabezado no provocan ninguna recompilación.

Si ya ha cambiado el archivo de encabezado en un momento en el que algunos archivos necesitan recompilarse, es demasiado tarde para hacerlo. En su lugar, puede utilizar el indicador `-o archivo ', que marca un archivo especificado como" antiguo ". Esto significa que el archivo en sí no se rehará y no se rehacerá nada más en su cuenta. debe seguir este procedimiento -

  • Vuelva a compilar los archivos fuente que necesitan compilación por razones independientes del archivo de encabezado particular, con `make -o header file '. Si hay varios archivos de encabezado involucrados, use una opción "-o" separada para cada archivo de encabezado.

  • Actualice todos los archivos de objeto con `make -t '.

En este capítulo, veremos otras características de Makefile.

Uso recursivo de Make

Uso recursivo de make significa usar makecomo un comando en un archivo MAKE. Esta técnica es útil cuando desea archivos MAKE separados para varios subsistemas que componen un sistema más grande. Por ejemplo, suponga que tiene un subdirectorio llamado `subdir 'que tiene su propio archivo MAKE, y le gustaría que se ejecute el archivo MAKE del directorio que lo contiene.makeen el subdirectorio. Puede hacerlo escribiendo el siguiente código:

subsystem:
   cd subdir && $(MAKE)

or, equivalently:
 	
subsystem:
   $(MAKE) -C subdir

Puedes escribir recursivo makecomandos simplemente copiando este ejemplo. Sin embargo, necesita saber cómo funcionan y por qué, y cómo se relaciona la sub-marca con la marca de nivel superior.

Comunicación de variables a una submarca

Valores variables del nivel superior makese puede pasar a la sub-marca a través del entorno mediante solicitud explícita. Estas variables se definen en la sub-marca como predeterminadas. No puede anular lo que se especifica en el archivo MAKE usado por el archivo MAKE de sub-make a menos que use la opción -e.

Para transmitir o exportar una variable, makeagrega la variable y su valor al entorno para ejecutar cada comando. La sub-marca, a su vez, usa el entorno para inicializar su tabla de valores de variables.

Las variables especiales SHELL y MAKEFLAGS siempre se exportan (a menos que las anule la exportación). MAKEFILES se exporta si lo configura en algo.

Si desea exportar variables específicas a una sub-marca, use la directiva de exportación, como se muestra a continuación:

export variable ...

Si desea evitar que se exporte una variable, use la directiva no exportar, como se muestra a continuación:

unexport variable ...

Los MAKEFILES variables

Si se define la variable de entorno MAKEFILES, makeconsidera su valor como una lista de nombres (separados por espacios en blanco) de archivos MAKE adicionales para ser leídos antes que los demás. Esto funciona de manera muy similar a la directiva include: se buscan esos archivos en varios directorios.

El uso principal de MAKEFILES es en la comunicación entre invocaciones recursivas del make.

Incluyendo archivo de encabezado de diferentes directorios

Si ha colocado los archivos de encabezado en diferentes directorios y está ejecutando makeen un directorio diferente, es necesario proporcionar la ruta de los archivos de encabezado. Esto se puede hacer usando la opción -I en makefile. Suponiendo que el archivo functions.h está disponible en la carpeta / home / tutorialspoint / header y el resto de los archivos están disponibles en la carpeta / home / tutorialspoint / src /, entonces el archivo MAKE se escribiría de la siguiente manera:

INCLUDES = -I "/home/tutorialspoint/header"
CC = gcc
LIBS =  -lm
CFLAGS = -g -Wall
OBJ =  main.o factorial.o hello.o

hello: ${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o [email protected] ${OBJS} ${LIBS}
.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

Agregar más texto a las variables

A menudo es útil agregar más texto al valor de una variable ya definida. Haga esto con una línea que contenga '+ =', como se muestra -

objects += another.o

Toma el valor de los objetos variables y le agrega el texto `otro.o ', precedido por un solo espacio como se muestra a continuación.

objects = main.o hello.o factorial.o
objects += another.o

El código anterior establece los objetos en `main.o hello.o factorial.o another.o '.

Usar '+ =' es similar a:

objects = main.o hello.o factorial.o
objects := $(objects) another.o

Línea de continuación en Makefile

Si no le gustan las líneas demasiado grandes en su Makefile, entonces puede romper su línea usando una barra invertida "\" como se muestra a continuación -

OBJ =  main.o factorial.o \
   hello.o

is equivalent to

OBJ =  main.o factorial.o hello.o

Ejecutar Makefile desde el símbolo del sistema

Si ha preparado el archivo Makefile con el nombre "Makefile", simplemente escriba make en el símbolo del sistema y se ejecutará el archivo Makefile. Pero si le ha dado otro nombre al Makefile, utilice el siguiente comando:

make -f your-makefile-name

Este es un ejemplo del Makefile para compilar el programa hello. Este programa consta de tres archivos main.cpp , factorial.cpp y hello.cpp .

# Define required macros here
SHELL = /bin/sh

OBJS =  main.o factorial.o hello.o
CFLAG = -Wall -g
CC = gcc
INCLUDE =
LIBS = -lm

hello:${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o [email protected] ${OBJS} ${LIBS}

clean:
   -rm -f *.o core *.core

.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

Ahora puedes construir tu programa hello utilizando el make. Si va a emitir un comandomake clean luego, elimina todos los archivos de objeto y los archivos principales disponibles en el directorio actual.