Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up go generate #6687

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ $Q echo "building $(or $(2), $(notdir $(1))) from internal/tools/go.mod..."
$Q cd internal/tools; go build -mod=readonly -o ../../$(BIN)/$(or $(2), $(notdir $(1))) $(1)
endef

# renames original binary $(BIN)/<binary> to $(BIN)/<binary>.bin
# and copy an associated script from ./scripts/gogenerate/<binary>.sh to $(BIN)/<binary>
#
# it is used when we want to replace an original binary with a gogenerate script
define replace_go_generate_script
$Q mv $(BIN)/$(1) $(BIN)/$(1).bin
$Q cp ./scripts/gogenerate/$(1).sh $(BIN)/$(1)
endef

# same as go_build_tool, but uses our main module file, not the tools one.
# this is necessary / useful for tools that we are already importing in the repo, e.g. yarpc.
# versions here are checked to make sure the tools version matches the service version.
Expand All @@ -183,6 +192,7 @@ $(BIN)/thriftrw-plugin-yarpc: go.mod go.work

$(BIN)/mockgen: internal/tools/go.mod go.work
$(call go_build_tool,go.uber.org/mock/mockgen)
$(call replace_go_generate_script,mockgen)

$(BIN)/mockery: internal/tools/go.mod go.work
$(call go_build_tool,github.com/vektra/mockery/v2,mockery)
Expand All @@ -200,6 +210,7 @@ $(BIN)/goimports: internal/tools/go.mod go.work

$(BIN)/gowrap: go.mod go.work
$(call go_build_tool,github.com/hexdigest/gowrap/cmd/gowrap)
$(call replace_go_generate_script,gowrap)

$(BIN)/revive: internal/tools/go.mod go.work
$(call go_build_tool,github.com/mgechev/revive)
Expand Down
48 changes: 48 additions & 0 deletions scripts/gogenerate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# GoGenerate

This folder contains custom scripts designed to replace the original binaries invoked during `go generate` commands.

## Why do we need these scripts?

In many cases, regenerating files during `go generate` is unnecessary if the source files (or other dependencies) have
not been updated.
These scripts introduce a simple optimization: they check file modification times and skip regeneration if the output
file is already up-to-date.

## How It Works

1. Each script should be called by `make go-generate` or `make pr`
1. Directly by `go generate` command will not work, because it will call the original binary that is not replaced by
the script
2. Each script intercepts the call to its respective binary (e.g., `mockgen`, `gowrap`) during `go generate`.
3. Before running the original command, the script checks:
- If the destination file is newer than its dependencies (e.g., source files, templates).
- If so, it skips regeneration and exits early.
4. If regeneration is necessary (i.e., dependencies are newer), the script executes the original binary with all
provided arguments.

## How to Debug

1. To enable debug logs run `GO_GENERATE_SCRIPTS_DEBUG=true make go generate ./...`.
2. Be sure that your script support this flag

## Add a new script

1. Save a new script in this folder with a name matching its corresponding binary (e.g., `mockgen.sh`, `gowrap.sh`).
2. Make them executable: `chmod +x script.sh`.
3. Add `replace_go_generate_script` to the `$(BIN)/<script>` section in Makefile
1. An original binary will be renamed to `<binary>.bin` and the new script will be copied to `./build/bin`.
4. Run `make clean` to remove the old binaries.
5. Run `make go-generate` to generate the new binaries

## Example Scripts

### `mockgen.sh`

Replaces the `mockgen` binary. It checks if the generated mock file is newer than the source file (`$GOFILE`). If so, it
skips regeneration.

### `gowrap.sh`

Replaces the `gowrap` binary. It ensures that the destination file is newer than both `$GOFILE` and the template file
before running the original `gowrap` command.
44 changes: 44 additions & 0 deletions scripts/gogenerate/gowrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

# Initialize variables
destinationFile=""
templateFile=""

# Parse arguments to find the -o (output) and -t (template) arguments
for ((i = 1; i <= $#; i++)); do
if [[ "${!i}" == "-o" ]]; then
# Capture the value of the -o argument (destination file)
nextIndex=$((i + 1))
destinationFile="${!nextIndex}"
elif [[ "${!i}" == "-t" ]]; then
# Capture the value of the -t argument (template file)
nextIndex=$((i + 1))
templateFile="${!nextIndex}"
fi
done

# Ensure destinationFile and templateFile are set
if [[ -z "$destinationFile" ]]; then
echo "Error: -o (output) argument is required."
exit 1
fi

if [[ -z "$templateFile" ]]; then
echo "Error: -t (template) argument is required."
exit 1
fi

# Check if destinationFile is newer than $GOFILE and templateFile
if [[ "$destinationFile" -nt "$GOFILE" && "$destinationFile" -nt "$templateFile" ]]; then
if [[ "$GO_GENERATE_SCRIPTS_DEBUG" == "true" ]]; then
echo "Skipped gowrap for $GOFILE"
fi
exit 0
fi

if [[ "$GO_GENERATE_SCRIPTS_DEBUG" == "true" ]]; then
echo "Run gowrap for $GOFILE"
fi

# Execute the original gowrap command with all arguments
gowrap.bin "$@"
41 changes: 41 additions & 0 deletions scripts/gogenerate/mockgen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Initialize variables
destinationFile=""

# Parse arguments to find the -destination argument
for arg in "$@"; do
if [[ "$arg" == "-destination" ]]; then
# Handle the case where -destination is followed by a separate value
nextIsDestination=true
elif [[ "$arg" == -destination=* ]]; then
# Handle the case where -destination=value is used
destinationFile="${arg#-destination=}"
elif [[ "$nextIsDestination" == true ]]; then
# Capture the value after -destination
destinationFile="$arg"
nextIsDestination=false
fi
done

# Ensure destinationFile is set
if [[ -z "$destinationFile" ]]; then
echo "Error: -destination argument is required."
exit 1
fi

# Check if destinationFile is newer than $GOFILE
if [[ "$destinationFile" -nt "$GOFILE" ]]; then
if [[ "$GO_GENERATE_SCRIPTS_DEBUG" == "true" ]]; then
echo "Skipped mockgen for $GOFILE"
fi
exit 0
fi

if [[ "$GO_GENERATE_SCRIPTS_DEBUG" == "true" ]]; then
echo "Run mockgen for $GOFILE"
fi

# Execute the original mockgen command with all arguments
# -write_command_comment=false to remove adding command comment
mockgen.bin "$@" -write_command_comment=false
Loading