---
sidebar_label: Build and push assets using Make
doc_id: 4e679186-03fb-4c72-9e57-6afcc6c59bd5
description: >-
  Guide for using GNU Make and Makefiles with nullplatform to build and push
  assets in CI/CD pipelines.
keywords:
  - GNU Make
  - Makefile
  - CI/CD
  - nullplatform
  - build automation
---

# Build and push assets using Make

This page describes how nullplatform uses [GNU Make](https://www.gnu.org/software/make/manual/html_node/index.html) and Makefiles for asset building and pushing to repositories.

Our GitHub Actions, GitLab Pipelines, Azure Pipelines, and more provide flexibility, allowing for default behavior, customization of Makefiles, and the use of custom build commands, arguments, and options.

## Behavior

When no customizations are provided, the `make` command is executed in your CI/CD pipeline using a Makefile.

The key steps involved are:

- **Makefile Discovery**: Our building process checks if a Makefile (`makefile` or `Makefile`) exists in the *build working directory* you provided. If not found, a default Makefile is used.

- **Default Rules**: If a Makefile is found, it looks for a default build rule named `build` and an upload-to-repository rule named `push`. If found, it executes these rules.

- **Example Command**: If no customizations are provided, the command executed is: `make -C BUILD_WORKING_DIRECTORY=. build` and `make -C BUILD_WORKING_DIRECTORY=. push`.

:::tip Execute make command locally
You can run these commands on your local machine to test your own Makefiles and simulate how the CI/CD pipeline builds your application. This can help you quickly identify errors.
:::

## Default Makefile Behavior

When no customizations are provided, the default Makefile is used. The key steps involved are:

- **Makefile Discovery**: Our building process checks if a Makefile (`makefile` or `Makefile`) exists in the *build working directory* you provided.

- **Default Rule**: If a Makefile is found, it looks for a default build rule named `build` and an upload-to-repository rule named `push`. If found, it executes these rules.

- **Example Command**: If no customizations are provided, the command executed is: `make -C BUILD_WORKING_DIRECTORY=. build` and `make -C BUILD_WORKING_DIRECTORY=. push`. You can execute these commands on your machine to test your Makefiles.

### Customizing Arguments and Options

You can change the *arguments* and *options* to customize the asset build or push while still using the default `make` command. For example:

- **Custom Arguments and Options**: By specifying different values for *arguments* and *options*, you can influence how the default Makefile's `build` rule is executed. For example, you can customize a Docker build argument by providing a `BUILD_ARGS` option.

- **Example Command**: If custom arguments and options are provided, the command executed might look like this: `make -C BUILD_WORKING_DIRECTORY=. BUILD_ARGS=--build-args=TOKEN=secret build` or `make -C BUILD_WORKING_DIRECTORY=. build custom-argument custom-option`.

### Custom Makefile

If you have a specific Makefile structure that depends on the *build working directory* and *asset working directory*, you can customize the Makefile based on these directories:

- **Makefile Location**: The GitHub Action checks for a Makefile in the *build working directory* provided. If found, it uses this Makefile.

- **Fallback to Defaults**: If no Makefile is found in the specified directory, it falls back to using the default Makefile.


## Advanced Customization

For advanced customization, you can provide a custom command, custom arguments, and options to execute a completely customized build process. This scenario allows you to specify the exact command you want to run:

- **Custom Command**: By specifying a custom command, you can execute a non-Makefile-based build process.

- **Custom Arguments and Options**: Additional arguments and options can be provided to fine-tune the custom command execution.

- **Example Command**: If a custom command is provided, the CI/CD pipeline runs it as-is, along with any specified arguments and options. For instance: `custom-command custom-argument custom-option`.

Understanding how Makefiles interact with the CI/CD pipeline is essential for effective asset building. By following the outlined scenarios and including recommended customizations, you can streamline the build process for your project's assets, whether using the default Makefile, custom Makefiles, or entirely custom build commands.

### Variables in the Makefile

To facilitate the `build` and `push` rules, several variables are used in the Makefile:

**BUILD_WORKING_DIRECTORY**: Specifies the directory where the build process takes place.  
**ASSET_WORKING_DIRECTORY**: Denotes the directory containing source code and assets for the build process.  
**ASSET_OUTPUT_DIRECTORY**: Specifies the directory where the built assets will be output.  
**ASSET_NAME**: The name of the asset being built or pushed.  
**ASSET_TYPE**: The type of asset, which determines which rules to apply.  
**ASSET_TARGET_URL**: The URL or location where the asset is pushed or deployed.  

### Recommended Rules

While the minimum required rules for build and push actions have been outlined, it is recommended to include the following rules in your custom Makefiles:

**setup**: This rule sets up the necessary environment or dependencies for the asset build.  
**test**: Use this rule to run tests related to the asset.  
**compile**: This rule is responsible for compiling or preparing the asset for packaging.  
**package**: The package rule bundles the asset into a deployable format.  
**push**: The push rule pushes the packaged asset to a target location, which can be a Docker registry or an S3 bucket, depending on the asset type.  


### Examples

#### Node.js Serverless Asset

```make
ASSET_FILE=$(ASSET_NAME).zip

.PHONY: all clean build setup test compile package push

all: clean build push
build: setup test compile package

clean:
	rm -r $(ASSET_OUTPUT_DIRECTORY)
setup:
	mkdir -p $(ASSET_OUTPUT_DIRECTORY)
	cp $(ASSET_WORKING_DIRECTORY)/package.json $(ASSET_OUTPUT_DIRECTORY)
	npm --prefix $(ASSET_OUTPUT_DIRECTORY) install --omit=dev
test:
	npm --prefix $(ASSET_WORKING_DIRECTORY) install --only=dev
	npm --prefix $(ASSET_WORKING_DIRECTORY) run test
compile:
	cp $(ASSET_WORKING_DIRECTORY)/*.js $(ASSET_OUTPUT_DIRECTORY)
package:
	cd $(ASSET_OUTPUT_DIRECTORY) && zip -r $(ASSET_FILE) .
push:
	aws s3 cp $(ASSET_OUTPUT_DIRECTORY)/$(ASSET_FILE) $(ASSET_TARGET_URL)
```

#### Docker image asset

```make
.PHONY: all clean build setup test compile package push

all: clean build push
build: setup test compile package

setup:
	docker build -t $(ASSET_NAME)-test --target test -f $(ASSET_WORKING_DIRECTORY)/Dockerfile .
test:
	docker run -it --rm --name $(ASSET_NAME)-test-run  $(ASSET_NAME)-test
compile:
package:
	docker build -t $(ASSET_NAME) --target build -f $(ASSET_WORKING_DIRECTORY)/Dockerfile .
push:
	docker tag $(ASSET_NAME):latest $(ASSET_TARGET_URL)
    	docker push $(ASSET_TARGET_URL)
```
