Description
Background
GopherJS ships with a set of "augmentations" to the standard library necessary to support JavaScript runtime. For user convenience, the augmentations are compiled into the gopherjs
tool and are applied on the fly by modifying AST on the fly. Thanks to this, the user doesn't need to worry about patching Go distribution and/or maintaining separate GOROOTs for GopherJS and plain Go. Additionally, the user can switch between any number of GOROOTs without having to do extra steps.
However, this approach has several drawbacks:
- These customizations are completely opaque to any kind of tooling: they can't be linted, vetted or used with any other tooling a user might want.
- Transformations are done every time a build is performed, repeatedly re-doing the same work and making the build a bit slower. Overall this probably isn't so bad thanks to the incremental build cache though.
- Such a sophisticated use case isn't well supported by the tooling Go provides (
go/build
andgolang.org/x/tools/go/packages
packages specifically). Specifically,go/build
only works in GOPATH mode when build context has any of the file system access function are overridden;go/packages
provides much more limited customization options: it either has to be done at ParseFile level, or by overriding whole contents of certain files in advance. So module support (module-aware building #855) becomes much more complicated than it has to be. - This is more of a personal opinion, but mixing source augmentation and build process together makes the relevant code considerably harder to understand and maintain.
Proposal
- Before attempting a build, materialize GOROOT with all augmentations in a temporary directory (
os.TempDir()
).- GOROOT from the environment is used as the original.
- Any files or directories from the original GOROOT that are not subject to augmentation are symlinked to the originals to conserve disk space.
- Files that do require augmentation are processed and written on disk.
- Path to the augmented GOROOT will include Go version from the original GOROOT as well as GopherJS version to allow for several different versions coexist on the same machine.
- Optionally, some form of fingerprinting may be used to make sure that the augmented GOROOT is only regenerated when the inputs change.
- Augmented GOROOT is considered disposable, and can be regenerated if it was deleted.
- During the build the augmented GOROOT is used instead of the original.
gopherjs root
subcommand is added so that the user can regenerate and get path to the augmented GOROOT for use with other tooling.
Extras
I did some early experiments and in general the approach seems viable. Complete regeneration of the augmented GOROOT took ~2-3 seconds on my 7 year old Core i5 laptop.
TinyGo also does something similar, although AFAICT they don't go as far as dynamic patching of the original sources, and replace whole packages instead with their custom implementations.