newmetric/govld
govld
is a custom linker (or kind of..) for Go, allowing patches without forking/replacing in the go.mod
.
govld
works by first running go mod vendor
to bring all dependencies to local, then patching listed files in the manifests provided.
How to Install?
# install rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# install govld
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install govld --git https://github.com/newmetric/govld
Usage
govld [-f] [-v=vendor_directory] -- [list_of_manifests.yaml]
-f/--force (default=false)
runsgo mod vendor
forcibly before patching.
Manifest File
Manifest file is a YAML file, containing a list of patches to be applied.
# sample patch manifest
file: github.com/fake-organization/pkg_b/internal/module.go
patch:
# replacing a simple function
- pattern: function_declaration
# optionally, you can also add certain imports
# below will be rendered as:
# import (
# aaa "github.com/fake-organization/pkg_a"
# )
imports:
- alias: aaa
path: github.com/fake-organization/pkg_a
code: |
func say() string {
return "World"
}
# replacing a method bound to a struct
- pattern: method_declaration
code: |
func (f Foo) privateMethod() string {
return "Newmetric was here"
}
# replacing struct itself
- pattern: struct_declaration
code: |
type Foo struct {
kkk int
aaa a.Pointer
added a.ArbitraryType
}
# replacing an empty struct
- pattern: struct_declaration
code: |
type emptyStruct struct {
notEmptyAnymore string
}
# replacing interface
- pattern: interface_declaration
code: |
type iface interface {
A() string
B() int
C() bool
}
# replacing global variable
- pattern: variable_declaration
code: |
var NewlyAdded string
# appending non-existent entry
- pattern: interface_declaration
code: |
type iface_appended interface {
A() string
B() int
C() bool
Appended() uint64
}
Optional Manifest
You can make the manifest optional by declaring optional: true
. Optional manifests won't fail even if the target patch file is not found.
# sample patch manifest
file: github.com/fake-organization/pkg_xxx/internal/should_not_panic.go
# don't panic even if the file is NOT found
optional: true
# usual patch...
patch:
# replacing a simple function
- pattern: function_declaration
code: |
func say() string {
return "World"
}
Postprocess
You can choose to run another batch of patches AFTER a manifest has been successfully processed. Simply append postprocess
section in the manifest file.
Note that postprocess
section is another array
- allowing you to set different targets for postprocessing. the yaml schema is the same as normal patch schema.
# run this AFTER all patches are done
postprocess:
- file: github.com/fake-organization/pkg_b/internal/postprocess_target.go
patch:
- pattern: method_declaration
code: |
func postprocessed() {}
# optional post process
- file: github.com/fake-organization/pkg_b/internal/postprocess_target_xx.go
optional: true
patch:
- pattern: method_declaration
code: |
func postprocessed() {}