Flutter comes with a rich set of commands to help you build your Flutter project features fast. You use these project commands to test or build your Flutter apps. These commands get complex when you have different build configurations and workflow automation. Often, you end up using a lot of commands, creating a bunch of shell scripts, and more.
GNU Make or Make helps simplify commands you need for your Flutter project. Although Make is not a 100% replacement for your shell scripts, Make compliments those.
Make and the Makefile
Make is a tool that controls the generation of executables for your program. Commonly used for building C++ source files. In the context of Flutter, you use Make to simplify your Flutter commands or scripts.
If you use Windows OS, read this guide on how to install Make on your machine.
To use Make, you need to create a Makefile
on the root directory of your project (relative to pubspec.yaml
):
touch Makefile
Make has concepts of rules which you can define to tell Make how to run or build your program.
targets: dependencies...
commands...
In Make, you can have a set of commands for your Flutter app that might look something like this:
run-prod:
$(FLUTTER) run --flavor prod --dart-define=is_something_enabled=false
deploy-prod:
$(FLUTTER) lint
$(FLUTTER) test
$(FLUTTER) run --flavor prod
Unlike having a god shell script, Makefile has a light syntax and an expressive way of declaring your tasks and their dependencies.
Make for Running Your Flutter Project
Flutter has an amazing list of helpful commands and allows you to add custom launch args by using --dart-define
, a.k.a. Dart Defines.
Flutter runs your project by simply calling flutter run
in your root project. It gets complex when you have a couple of build flavors and configurations.
For example, running your app with a custom flavor for development:
flutter run --flavor dev
or your app with a custom flavor and launch arguments:
flutter run --flavor dev --dart-define=use_crashlytics_logging=false --dart-define=use_sentry_logging=false
Whatever your launch commands are, they should be documented so that others know how to interact with your project.
To use Make commands for running your Flutter project, add the following to your Makefile
:
.PHONY: run
run:
flutter run --flavor dev --dart-define=use_crashlytics_logging=false --dart-define=use_sentry_logging=false
.PHONY: build
build:
flutter run --flavor dev
.PHONY: run-prod
run-prod:
flutter run --flavor prod --dart-define=use_crashlytics_logging=false --dart-define=use_sentry_logging=false
.PHONY: build-prod
build-prod:
flutter run --flavor prod
.PHONY: run-components
run-components:
flutter run --flavor dev --dart-define=show_components_only=true
Then, you can launch your Flutter project like:
make run
Make for Generating Dart Code
In a typical development setup, you almost always have a command for generating the Dart code using build_runner.
Most of the time, you run:
flutter pub run build_runner build --delete-conflicting-outputs
or without the conflict flag:
flutter pub run build_runner build
In some of my projects, I also have apps that use build_runner’s multiple configurations.
In the build.yaml
, you can define custom configurations:
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
compiler: dart2js
dev_options:
dart2js_args:
- --no-minify
release_options:
dart2js_args:
- -O3
and you call the build option by passing a --config
value, for example, building for release:
flutter pub run build_runner build --config release
To use Make commands for generating Dart code, add the following to your Makefile
:
.PHONY: codegen-cached
codegen-cached:
flutter pub run build_runner build
.PHONY: codegen
codegen:
flutter pub run build_runner build --delete-conflicting-outputs
.PHONY: codegen-release
codegen-release:
flutter pub run build_runner build --delete-conflicting-outputs --config release
Then, you can run the build_runner
commands like:
make codegen
Make for Flutter CI Automation
If you’re here, you’ve probably already advanced in Flutter and make use of CI/CD platforms like Github Actions, CircleCI, and Bitrise for automation. Using these systems, you interact with CI/CD environments using CLI commands.
Depending on the CI platform are you using, you can create custom Make rules for each.
This is an example config file when you are using Github Actions:
name: Main Workflow
on: push
concurrency:
group: main-${{ github.ref }}
cancel-in-progress: true
env:
flutter_sdk: "3.0.2"
java_sdk: "12.x"
jobs:
main:
name: Main Job
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: ${{ env.java_sdk }}
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.flutter }}
# Example use of GNU Make starts here
- name: Generate Code
run: make codegen
- name: Run Unit Tests
run: make tests
- name: Run GUI Tests
run: make integration-tests
- name: Build Dev Build
run: make build
- name: Deploy Dev Build
run: make deploy-build
# Example use of GNU Make ends here
You can create as many rules as you’d like here, but make sure it’s going to be helpful for the rest of the team and the project.
Other use cases
There are other areas in your project where Make can be helpful like for git pre-commits, code lint and formatting, generating code coverage reports, and many more.
Here’s an example of a Makefile you can use for the default Flutter app:
ROOT := $(shell git rev-parse --show-toplevel)
FLUTTER := $(shell which flutter)
FLUTTER_BIN_DIR := $(shell dirname $(FLUTTER))
FLUTTER_DIR := $(FLUTTER_BIN_DIR:/bin=)
DART := $(FLUTTER_BIN_DIR)/cache/dart-sdk/bin/dart
# Flutter
.PHONY: analyze
analyze:
$(FLUTTER) analyze
.PHONY: format
format:
$(FLUTTER) format .
.PHONY: test
test:
$(FLUTTER) test
.PHONY: codegen
codegen:
$(FLUTTER) pub run build_runner build --delete-conflicting-outputs
.PHONY: run
run:
$(FLUTTER) run
# Git
.PHONY: fetch-main
fetch-main:
$(shell git fetch origin main)
.PHONY: rebase-main
rebase-main:
$(shell git pull --rebase origin main)
It’s a wrap!
Make is an alternative way to work with the commands of your Flutter projects. It helps you define and self-document tasks which are helpful for maintaining projects at scale, or when onboarding new engineers.
Other tools like using custom extensions for VS Code and your preferred IDE can also help simplify your life. Although, these extensions and IDE specific aren’t something you can use in your automation, eg. running IDE extensions for launch configurations in CI/CD.
Give it a try today!