bgo
One sentence to understand: bgo pipe exe regardless of lib
foreword
Preface before :
The preface this time is very long and can be skipped directly.
bgo is a build aid that is not much different from the known tools for building services for go applications, and supports cross-compiling the executable to build the go application in your working environment. <!--MORE-->
Although this capability is originally built into go build, the auxiliary build tools that are packaged once can save you a lot of effort, whether it's those hard-to-remember go build command-line parameters, or those we have to use Pre-prep, post-processing steps such as go generate, copy to a public location, etc.
So that's what tools like bgo are for, save effort.
origin
Of course, why should I develop yet another helper tool?
The reasons are not complicated, there are several:
I often make a lot of small projects in a big directory, maybe just to test a certain function, do a certain thing, maybe because I need to list a lot of examples for users to see, in short, there are many. But in a small cycle, I usually focus on one or two or three of them. For this reason, switching directories and building are very tired. Goland is very considerate and allows me to save the work in these command line environments, but its running result window cannot support full-featured keyboard interaction, so I can only open a terminal to run for some behaviors. Of course, there are many other things that Goland must not be able to do, so I won't list them. In short, this is a specific problem. For this, why not vscode? Have you used it? You need to know that vscode only has the ability to debug and run a certain main program when running goapp. If you want to debug or not, or want to have multiple apps, you need to type the command line in its embedded terminal. Also, vscode has a lot of problems if you don't open the workspace in go.mod - in other words, vscode supports at most modules represented by a single go.mod.
Another reason is that as a more formal release, I may need to use -X or go generate frequently. For this, I originally had a complex Makefile that would solve these problems automatically. But they are not very useful for me in large scale projects, because I may have many different directories corresponding to different submodules.
Another reason is the order of builds, I sometimes need to do a series of builds in an orderly manner, and sometimes I need to quickly build a single target to run in exeutable mode and view the results.
Of course there are other reasons, but I won't talk about it.
You can notice that the main reason is again that I am maintaining a large project structure. Why not split into many, many repos? This brings up another problem: go modules do not support multiple nested go.mods. That is to say, if your upper level directory has go.mod, then the lower level cannot have it anymore. This is a very complicated situation, but in the end, this restriction is very hard for you, so this structure will not work:
/complex-system
go.mod
/api
/v1
go.mod
/common
go.mod
/account
go.mod
/backends
/order
go.mod
/ticket
go.mod
Only a part of the directory structure is listed here, and it is only an indication.
Likewise, there are still people who say that every go.mod is just a repo. Well, it is like this, it is indeed possible to do this, and it can only be done in this way.
After that, you need to find a way to resolve the dependencies.
go 1.18's go.work doesn't help, or even makes it hard to use.
git submodules? Very troublesome, difficult to use, will forget the state, and then the cup.
Then some people will say, the top-level go.mod, don't use the others, just separate the directory structure. Yeah, that's what I did when I was working on a similar large scale project a few years ago, but the api's protobuf reference, the common's etcd reference, etc. were all mixed up, and they were all kind of cumbersome to solve, and in the end Makes the whole mega project bloated and needlessly cluttered.
In fact, there was a time when multiple go.mod nested existences could work sloppily, but that stage was very short, I forgot whether it was in 1.13 or which period, in short, this is a distant memory. I was satisfied with the configuration of the project during that time, but then it was more painful to re-conform to a single go.mod.
so
There are a lot of reasons, but bgo only solves part of the problem, that is, the build problem when many small apps are distributed in a bunch of complex subdirectories.
With bgo, we can use a large top-level directory to manage several sub-projects. Of course, due to the limitation of go modules, go.mod cannot be created in the top-level directory (to avoid problems with go.mod in subdirectories) , it only plays the role of aggregation, and we use Goland or vscode to open this structure will be OK.
Current configuration:
/atonal
.bgo.yml
/api
/v1
go.mod
/common
go.mod
...
You can notice that there is a .bgo.yml
file in the top-level directory. This is the bgo configuration file. It can be bgo init
after scanning subdirectories, and then you can further adjust it on this basis.
The only problem is that it only collects the directory where the main package is located, that is, those directories that can produce executables. As for your directory with go.mod, it is not really included in the management scope.
This is a limitation of bgo, but that's what bgo was designed for: we scan and manage a set of CLI apps and build accordingly in a lighter way. We don't provide the management of multiple modules marked by go.mod. Either we design a separate tool for this function, or we don't do it, lest google get a nerve and come up with a set of go.zone one day.
Of course, you can have a main.go for each go.mod and let bgo take care of it. But in general, bgo pipe exe regardless of lib
Disappointing go evolution
Saying go.mod go.work have it all, but not all effective programs to do large-scale projects to solve, but merely confined I in one module of.
In project management in Go, there are always many questions:
- Solution for private repos
- Problem repo or solution for missing repo
- Complex multi-module construction and organization
- Poor generics, might as well not have
- etc
To be honest, I had high expectations. But my mood is probably similar to those of yaml, wow, generics are here, oh, generics go well. In this area (automatic type resolution, unknown type resolution), golang's generics are pretty much useless. According to the current evolution situation, it is impossible to expect it to solve the unknown type of free pre-judgment.
How to get started?
Install
The first thing to do is, of course, to install bgo.
Releases
bgo is designed to work with just a single executable, so please Releases and put it in your search path.
go get
Alternatively, you can pass the go get system:
go get -u -v github.com/hedzr/bgo
From source code
Homebrew
homebrew is ok:
brew install hedzr/brew/bgo
Package management of other platforms is not supported yet, because this is just a small tool.
Docker
It can also be run using docker, but it is a little more complicated because it needs to mount the native volume:
docker run -it --rm -v $PWD:/app -v /tmp:/tmp -v /tmp/go-pkg:/go/pkg hedzr/bgo
This is the same as executing native bgo.
docker containers can be obtained from these places:
docker pull hedzr/bgo:latest
docker push ghcr.io/hedzr/bgo:latest
run
In the directory you want to build, run bgo.
For example we run it in the source code of ini-op
This is the easiest way to start.
Features
But if that's the case, what's the value? Command line shortener?
What bgo can do, especially in and :
- tags etc. go build command line is too long and hard to edit
- Want to orchestrate build instructions and their pre- and post-processing behavior. For example adding post processors, etc.
- There are a whole bunch of subdirectories that have CLIs, but don't want to build them one by one, or have different build parameters that can't be dealt with all at once.
- There is a series of go modules, but want to organize them uniformly.
These goals can be solved through configuration files.
Create a build profile
Based on the current directory, bgo will look for the .bgo.yml
file in the current directory, load the projects configuration information in it, and then build it in a specific order.
Since we emphasize orderliness, bgo does not support parallel compilation of multiple projects.
In fact, this feature is worthless because it just slows down the overall compilation speed.
So the first question is, .bgo.yml
prepared?
To do this, select your root directory to run bgo for the first time.
bgo -f --save # Or: bgo init
mv bgo.yml .bgo.yml
This command will scan all subdirectories, then compile a build configuration table and save it as bgo.yml
file.
You can then optionally rename the configuration file (this is recommended to prevent possible side effects of --save
But if you want, you can keep the filename as it is.
synonym
bgo init
bgo init --output=.bgo.yml
If you need an annotated example, see:
https://github.com/hedzr/bgo/blob/master/.bgo.yaml
start building
Once the configuration file is ready, you can run:
bgo
This will build all the projects in the configuration file.
In fact, if there is no configuration file, bgo will automatically scan out the first main cli and build it.
Take a different build range
bgo supports three scopes, which differ in how projects in the configuration file are handled and whether or not projects in the current folder are scanned:
[Scope?]
-a, --auto ⬢ Build all modules defined in .bgo.yaml recursively (auto mode) [env: AUTO] (default=true)
-f, --full ⬡ Build all CLIs under work directory recursively [env: FULL](default=false)
-s, --short ⬡ Build for current CPU and OS Arch ONLY [env: SHORT] (default=false)
Usually bgo is in automatic mode ( --auto
), at this time only projects in the configuration file are considered for inclusion in the build sequence. If no configuration file is found, bgo will try to scan the current folder for the first main app.
The full scan mode ( --full
) scans all possible projects in the current folder.
The short mode ( --short
) will only extract the first project from the config file, however, if you don't have a config file in your root directory, it will scan to the first project to build. What makes it special is that only the current GOOS+GOARCH of the worker will be built.
In addition to that, you can explicitly specify a name to run bgo. bgo will retrieve the project with the same name in the configuration table and run the build sequence on it individually.
bgo -pn whoami # Or `--project-name`
In the configuration file, you can set disabled: true
to ban a project, or to ban a project group.
group projects
Multiple projects can be divided into a group, the advantage is that the build parameters can be specified uniformly. For example:
---
app:
bgo:
build:
cgo: false
projects:
000-default-group:
# leading-text:
cgo: false
items:
001-bgo: # <- project name
name: # <- app name
dir: .
gen: false
install: true
cgo: false
010-jsonx: # <- project name
name: # <- app name
dir: ../../tools/jsonx
The example defines a default-group
group, and then defines two projects bgo
and jsonx
Their name prefixes (like the 001-
fragment) are used for sorting purposes, are ignored in build behavior, and are not considered part of the name.
A project group's build configuration settings (such as cgo parameters) will be dropped and applied to each project unless you specify it explicitly; similarly, cgo, for, os, arch, etc. parameters can also be defined app.bgo.build
They have a higher priority.
In the sample snippet, the cgo parameter is defined at all levels, and the project-level cgo has the highest priority.
Project configuration parameters
Except for the name, dir, package fields, other parameters are public. That is, other parameters can be used at the top or item group level.
name, dir, package
Every project needs dir as a basic definition, which is required:
---
app:
bgo:
build:
projects:
000-default-group:
# leading-text:
items:
001-bgo: # <- project name
name: # <- app name
dir: .
package: # optional
You can use ../
to escape the limitation of the current directory. In theory, you can include everything.
bgo will detect go.mod in this directory to decide whether it should start go modules compilation.
Whenever go build is specifically executed, it will always switch to the location pointed to by dir, based on this, unless you specify keep-workdir
, which tells bgo to keep the current working directory. You can also use use-workdir
to specify a go build base working directory for a project.
name
can be used to explicitly specify the app name of the project. If not specified, the value will be taken from the project name. The project name is the developer's project management name, and the app name is what the end user sees (and, the base name of the executable that will be output as output).
package
is optional, usually you don't have to specify it manually. This field is automatically filled in the results scanned by bgo init
, but it is always re-extracted when actually building.
disabled
This field causes the corresponding item to be skipped
disable-result
After bgo has completed the build, it will automatically make the result executable. This field can disable this behavior of bgo.
keep-workdir, use-workdir
Under normal circumstances, bgo jumps to the directory where the project is located and starts go build .
, and then returns to the current directory.
But keep-workdir
allows you to stay in the current directory (that is, the directory where bgo was started), and build go build ./tools/bgo
At this point you can set keep-workdir to true.
If you want a project to use a specific base directory (most likely because that base directory has go.mod), you can specify one use-workdir
use-workdir: ./api/v1
keep-workdir: false
gen, install, debug, gocmd, cgo, race, msan,
gen
determines whether to run go generate ./... before go build
install
determines whether to copy the executable file into $GOPATH/bin
, as go install
does.
debug
will produce a larger executable, and by default will use the typical configuration of -trimpath -s -w to reduce the size of the executable.
gocmd
when you want to perform builds with different go versions, just set it to point to your specific version of go executable. Most of the time you will probably keep it empty. But gocmd can be useful if you've compiled a piece of experimental code in a folder that requires generics.
cgo
determines the value of the environment variable CGO_ENABLED
and whether to enable the gcc link during build. Note that the CGO feature is only available for current GOOS/GOARCH, it is not well supported for cross-compilation.
race
indicates whether to enable race condition detection.
msan
is passed as-is to go build to produce memory diagnostics and audit code.
Target platforms: for
, os
, arch
In the configuration file, you can specify which target platforms to build against.
---
app:
bgo:
build:
# the predefined limitations
# for guiding which os and arch will be building in auto scope.
#
# If 'bgo.build.for' is empty slice, the whole available 'go tool dist list'
# will be used.
#
#for:
# - "linux/amd64"
# - "windows/amd64"
# - "darwin/amd64"
# - "darwin/arm64"
# the predefined limitations
os: [ linux ]
# the predefined limitations
#
arch: [ amd64,"386",arm64 ]
Note that these keys can also be used for project-group or project, for example:
projects:
000-default-group:
# leading-text:
items:
001-bgo: # <- project name
name: # <- app name
dir: .
gen: true
install: true
# os: [ "linux","darwin","windows" ]
# arch: [ "amd64" ]
# for: [ "linux/riscv64" ]
for specifies an array of target platforms, where each entry is an os/arch pair.
But if you specify the os and arch arrays, they will do a Cartesian product to produce the final target platform martrix.
post-action,pre-action
A shell script can be specified to be executed before or after go build.
A post-action might look like this:
post-action: |
if [[ "$OSTYPE" == *{ {.OS}}* && "{ {.Info.GOARCH}}" == { {.ARCH}} ]]; then
cp { {.Output.Path}} $HOME/go/bin/
fi
echo "OS: $OSTYPE, Arch: { {.Info.GOARCH}}"
It uses the template expansion feature. The corresponding data source comes from our build.Context
variable. The definition of this variable will be described at the end of the article.
Specifically, { {.Info.GOARCH}}
represents the running go runtime value, which is runtime.GOARCH
, while { {.OS}}
and { {.ARCH}}
are the corresponding values for the target being built.
Due to jekyll template expansion, all { {
spaces are inserted to prevent the article from failing.
post-action-file,pre-action-file
You can use a script file if you want.
Notice
These settings are per-project only and do not support being applied to the project-group level. The reason is that the group level is removed in the final stage of our code implementation.
ldflags, asmflags, gcflags, tags
These optional arguments will be passed to the corresponding command line arguments to go build.
But in our case, you should specify them as arrays, for example:
---
app:
bgo:
build:
ldflags: [ "-s", "-w" ]
The global ldflags parameter specified will be used when all projects are built, unless you explicitly specify a specific version of ldflags in a project.
extends
Writing variable values to specific packages of code being built is accomplished with go build -ldflags -X ...
. There are also configuration file entries to simplify the problem:
001-bgo: # <- project name
name: # <- app name
dir: tools/bgo
gen: false
install: true
cgo: true
extends:
- pkg: "github.com/hedzr/cmdr/conf"
values:
AppName: "{ {.AppName}}"
Version: "{ {.Version}}"
Buildstamp: "{ {.BuildTime}}" # or shell it
Githash: "`git describe --tags --abbrev=16`"
# Githash: "{{.GitRevision}}" # or shell it: "`git describe --tags --abbrev=9`"
GoVersion: "{ {.GoVersion}}" # or shell it
ServerID: "{ {.randomString}}"
Template expansion can be used, or small shell scripts can be embedded.
But writing a lot of scripts here is discouraged.
The examples are given as hedzr/cmdr CLI app, but in reality they are redundant: as a family we automatically try to identify if your go.mod contains a reference to cmdr, so Decided whether we want to automatically fill in this set of parameters.
Obviously, writing package variables at build time is not something specific to cmdr, so you can use it for your own configuration.
Top-level specific configuration parameters
In the configuration file, app.bgo.build
is the top level of the configuration item, where exclusion directories and output filename templates can be specified:
---
app:
bgo:
build:
output:
dir: ./bin
# split-to sample: "{ {.GroupKey}}/{ {.ProjectName}}"
#
# named-as sample: "{ {.AppName}}-{ {.Version}}-{ {.OS}}-{ {.ARCH}}"
# wild matches with '*' and '?'
# excludes patterns will be performed to project directories.
# but projects specified in .bgo.yml are always enabled.
excludes:
- "study*"
- "test*"
output
block, you can specify named-as
as the template for outputting the executable file name. By default, bgo will use { {.AppName}}-{ {.OS}}-{ {.ARCH}}
.
dir
specifies the output folder, where the executable is pointed.
When you can also specify split-to
to set additional sub-file levels for each project, such as { { .ProjecName}}
, and so on.
excludes
is an array of strings that provide a set of filename wildcard templates, and folders matching these templates will not be scanned.
Build Context
In some fields we allow you to embed dynamic variable values, which will change based on the build time of each project. For example { {.AppName}}
can be expanded to the app name of the project currently being built.
These values are included in the declaration of build.Context
The corresponding code snippet from the bgo source code is given below, so I don't have to explain it any further.
This is not the latest version, it is still iterating
type (
Context struct {
WorkDir string
TempDir string
PackageDir string
// Output collects the target binary executable path pieces in building
Output PathPieces
*Common
*Info
*DynBuildInfo
}
)
type PathPieces struct {
Path string
Dir string
Base string
Ext string
AbsPath string
}
type (
Info struct {
GoVersion string // the result from 'go version'
GitVersion string // the result from 'git describe --tags --abbrev=0'
GitRevision string // revision, git hash code, from 'git rev-parse --short HEAD'
BuildTime string //
GOOS string // a copy from runtime.GOOS
GOARCH string // a copy from runtime.GOARCH
GOVERSION string // a copy from runtime.Version()
RandomString string
RandomInt int
Serial int
}
DynBuildInfo struct {
ProjectName string
AppName string
Version string
BgoGroupKey string // project-group key in .bgo.yml
BgoGroupLeadingText string // same above,
HasGoMod bool //
GoModFile string //
GOROOT string // force using a special GOROOT
Dir string
}
)
type (
CommonBase struct {
OS string `yaml:"-"` // just for string template expansion
ARCH string `yaml:"-"` // just for string template expansion
Ldflags []string `yaml:"ldflags,omitempty,flow"` // default ldflags is to get the smaller build for releasing
Asmflags []string `yaml:"asmflags,omitempty,flow"` //
Gcflags []string `yaml:"gcflags,omitempty,flow"` //
Gccgoflags []string `yaml:"gccgoflags,omitempty,flow"` //
Tags []string `yaml:"tags,omitempty,flow"` //
// Cgo option
Cgo bool `yaml:",omitempty"` //
// Race option enables data race detection.
// Supported only on linux/amd64, freebsd/amd64, darwin/amd64, windows/amd64,
// linux/ppc64le and linux/arm64 (only for 48-bit VMA).
Race bool `yaml:",omitempty"` //
// Msan option enables interoperation with memory sanitizer.
// Supported only on linux/amd64, linux/arm64
// and only with Clang/LLVM as the host C compiler.
// On linux/arm64, pie build mode will be used.
Msan bool `yaml:",omitempty"` //
Gocmd string `yaml:",omitempty"` // -gocmd go
Gen bool `yaml:",omitempty"` // go generate at first?
Install bool `yaml:",omitempty"` // install binary to $GOPATH/bin like 'go install' ?
Debug bool `yaml:",omitempty"` // true to produce a larger build with debug info
DisableResult bool `yaml:"disable-result,omitempty"` // no ll (Shell list) building result
// -X for -ldflags,
// -X importpath.name=value
// Set the value of the string variable in importpath named name to value.
// Note that before Go 1.5 this option took two separate arguments.
// Now it takes one argument split on the first = sign.
Extends []PackageNameValues `yaml:"extends,omitempty"` //
CmdrSpecials bool `yaml:"cmdr,omitempty"`
}
PackageNameValues struct {
Package string `yaml:"pkg,omitempty"`
Values map[string]string `yaml:"values,omitempty"`
}
Common struct {
CommonBase `yaml:"base,omitempty,inline,flow"`
Disabled bool `yaml:"disabled,omitempty"`
KeepWorkdir bool `yaml:"keep-workdir,omitempty"`
For []string `yaml:"for,omitempty,flow"`
Os []string `yaml:"os,omitempty,flow"`
Arch []string `yaml:"arch,omitempty,flow"`
Goroot string `yaml:"goroot,omitempty,flow"`
PreAction string `yaml:"pre-action,omitempty"` // bash script
PostAction string `yaml:"post-action,omitempty"` // bash script
PreActionFile string `yaml:"pre-action-file,omitempty"` // bash script
PostActionFile string `yaml:"post-action-file,omitempty"` // bash script
}
)
For the latest version, please go directly or check it out at go.dev.
Use of the command line
bgo is a hedzr/cmdr , with basic features supported by cmdr, such as free multi-level subcommand parameter input and recognition, and so on.
In general, you should use bgo in two steps:
Use
bgo init
generate abgo.yml
template, which organizes the scanned cli apps into this configuration template, please rename it to.bgo.yml
facilitate automatic loading of bgo.bgo looks for the existence of a .bgo.yml file from several automatic locations. In fact it will also look at bgo.yml, and will automatically expand to the corresponding conf.d folder for autoloading and merging, so you can write a separate yaml fragment for each project in conf.d, which It's great for continuous integration.
- Once the .bgo.yml is ready (you can manually edit it to add custom properties), just use
bgo
one-time build.
In addition to the above automatic mode, there are also some ways to leave the automatic mode for temporary specific operations.
- If bgo doesn't find a configuration file, we try to scan the current folder to try to build.
- If you don't want unexpected build behavior, take the
--dry-run
parameter.
Some potentially useful command lines are listed below, and none of them require a configuration file to be present - but as mentioned, a configuration file can give you a lot of control in an easily tweakable format, and the command line is no matter what Optimization, nor can it hide -ldflags
is so complex and difficult to edit.
To shorten command line typing, running bgo is implicitly equivalent to executing the bgo build subcommand. That is, bgo -s
essentially executes bgo build -s
, which will start a short build mode.
So to see possible command line arguments you should use bgo build --help
.
Build against current GOOS/GOARCH
bgo -s
bgo -s -pn project-one
The purpose of the latter form is to compile the project-one project only for the current GOOS/GOARCH, ignoring other projects in the configuration file.
If there is no configuration file, it will automatically find the first main cli and build.
full scan mode
bgo -f
At this time, in addition to the projects defined in the configuration file, bgo will scan all cli apps under the folder again.
Specify the build target platform
For example, only compile the linux/386 executable file for the specified target platform, ignoring other target platform definitions that may exist in the configuration file:
bgo build --for linux/386
bgo -os linux -arch 386
Both commands serve the same purpose.
At the same time, an array can be specified by specifying it multiple times:
bgo -os linux --arch 386 --arch amd64,arm64
And feel free to use the comma delimiter ( ,
) to tell bgo to recognize a list of arrays, for example the above example actually gives an array of three [ "386", "amd64", "arm64"]
as arguments: 061f60d4f9ae45 .
In addition,for
,os
andarch
are applicable to both long and short parameter forms,--for
or-for
no problem, the purpose is to reduce the memory burden.
Similarly, this is also valid:
bgo --for linux/386 --for darwin/am64
bgo --for linux/386,darwin/amd64
Specify the build project name
bgo -pn bgo
bgo
used as the build abbreviation and is a shortened waybgo build
You can qualify both the project name and the target platform:
bgo -os linux,windows -arch 386,amd64 -pn project-one
Enable Shell Autocomplete
Currently bgo can provide Shell auto-completion function. After entering bgo, type TAB .
as well as
and
If you are running bgo by downloading the binary executable, a few steps are required to enable shell autocompletion.
zsh
For the zsh environment, the autocomplete script is generated like this:
$ bgo gen sh --zsh
# "/usr/local/share/zsh/site-functions/_bgo" generated.
# Re-login to enable the new zsh completion script.
If bgo can't find a place to put the _bgo
completion script, it will output the script to the console, you need to save it _bgo
as 061f60d4f9b185 and put it in your zsh autocomplete script search location.
zsh uses the environment variable fpath to indicate where the autocomplete script should be placed. For example:
❯ print -l $fpath /Users/hz/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /Users/hz/.oh-my-zsh/custom/plugins/zsh-autosuggestions /Users/hz/.oh-my-zsh/plugins/z /Users/hz/.oh-my-zsh/plugins/docker /Users/hz/.oh-my-zsh/plugins/git /Users/hz/.oh-my-zsh/functions /Users/hz/.oh-my-zsh/completions /Users/hz/.oh-my-zsh/cache/completions /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.7.1/functions
bgo
will automatically interpret these path locations and find the best placement path.bgo
cannot be successfully written due to write permissions or other issues, then you need to do it manually.
You can also generate the script to a specified location:
bgo gen sh --zsh -o /some/where/for/_bgo
bash
bash autocomplete script
postscript
bgo may seem useful, but it probably won't work for you either.
bgo is like a modules manager and build aid, but it is not actually a modules manager, at most it can only be regarded as an automatic builder for main packages.
So the important thing is said three times: bgo pipe exe regardless of lib.
At last
In the end, I have to say that what bgo did is very rude, because the initial idea was just to have a Disabled flag to kill certain projects, and then I wanted to have a --project-name to filter, then -os, -arch , and then found it necessary to have --for.
And then it was almost out of control.
But anyway it can run now.
title map
Off topic
Anyone else remember Newegg?
Suddenly I saw some news about newegg. It turns out that its American parent company is alive and well, far from being closed down and declining, and it went public last year.
At this age change, looking back on the changes on the Internet in the past 20 years, my heart is filled with emotion but it is difficult to evaporate.
REFs
- GOOS/GOARCH combos on macOS - Marcelo Cantos
- Project: https://github.com/hedzr/bgo
- Example configuration file: https://github.com/hedzr/bgo/blob/master/.bgo.yaml
- Docker Hub: hedzr/bgo - Docker Image | Docker Hub
🔚
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。