mirror of https://github.com/k3d-io/k3d
parent
bf695e533e
commit
5cd9aa1d08
@ -1,9 +1,57 @@ |
|||||||
package image |
package image |
||||||
|
|
||||||
import ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
import "github.com/docker/docker/api/types/filters" |
||||||
|
|
||||||
// GetImageOpts holds parameters to inspect an image.
|
// ImportOptions holds information to import images from the client host.
|
||||||
type GetImageOpts struct { |
type ImportOptions struct { |
||||||
Platform *ocispec.Platform |
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
|
||||||
Details bool |
Message string // Message is the message to tag the image with
|
||||||
|
Changes []string // Changes are the raw changes to apply to this image
|
||||||
|
Platform string // Platform is the target platform of the image
|
||||||
|
} |
||||||
|
|
||||||
|
// CreateOptions holds information to create images.
|
||||||
|
type CreateOptions struct { |
||||||
|
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
|
||||||
|
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
|
||||||
|
} |
||||||
|
|
||||||
|
// PullOptions holds information to pull images.
|
||||||
|
type PullOptions struct { |
||||||
|
All bool |
||||||
|
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||||
|
|
||||||
|
// PrivilegeFunc is a function that clients can supply to retry operations
|
||||||
|
// after getting an authorization error. This function returns the registry
|
||||||
|
// authentication header value in base64 encoded format, or an error if the
|
||||||
|
// privilege request fails.
|
||||||
|
//
|
||||||
|
// Also see [github.com/docker/docker/api/types.RequestPrivilegeFunc].
|
||||||
|
PrivilegeFunc func() (string, error) |
||||||
|
Platform string |
||||||
|
} |
||||||
|
|
||||||
|
// PushOptions holds information to push images.
|
||||||
|
type PushOptions PullOptions |
||||||
|
|
||||||
|
// ListOptions holds parameters to list images with.
|
||||||
|
type ListOptions struct { |
||||||
|
// All controls whether all images in the graph are filtered, or just
|
||||||
|
// the heads.
|
||||||
|
All bool |
||||||
|
|
||||||
|
// Filters is a JSON-encoded set of filter arguments.
|
||||||
|
Filters filters.Args |
||||||
|
|
||||||
|
// SharedSize indicates whether the shared size of images should be computed.
|
||||||
|
SharedSize bool |
||||||
|
|
||||||
|
// ContainerCount indicates whether container count should be computed.
|
||||||
|
ContainerCount bool |
||||||
|
} |
||||||
|
|
||||||
|
// RemoveOptions holds parameters to remove images.
|
||||||
|
type RemoveOptions struct { |
||||||
|
Force bool |
||||||
|
PruneChildren bool |
||||||
} |
} |
||||||
|
@ -1,138 +1,35 @@ |
|||||||
package types |
package types |
||||||
|
|
||||||
import ( |
import ( |
||||||
"github.com/docker/docker/api/types/checkpoint" |
|
||||||
"github.com/docker/docker/api/types/container" |
|
||||||
"github.com/docker/docker/api/types/image" |
"github.com/docker/docker/api/types/image" |
||||||
"github.com/docker/docker/api/types/swarm" |
|
||||||
"github.com/docker/docker/api/types/system" |
|
||||||
) |
) |
||||||
|
|
||||||
// CheckpointCreateOptions holds parameters to create a checkpoint from a container.
|
// ImageImportOptions holds information to import images from the client host.
|
||||||
//
|
//
|
||||||
// Deprecated: use [checkpoint.CreateOptions].
|
// Deprecated: use [image.ImportOptions].
|
||||||
type CheckpointCreateOptions = checkpoint.CreateOptions |
type ImageImportOptions = image.ImportOptions |
||||||
|
|
||||||
// CheckpointListOptions holds parameters to list checkpoints for a container
|
// ImageCreateOptions holds information to create images.
|
||||||
//
|
//
|
||||||
// Deprecated: use [checkpoint.ListOptions].
|
// Deprecated: use [image.CreateOptions].
|
||||||
type CheckpointListOptions = checkpoint.ListOptions |
type ImageCreateOptions = image.CreateOptions |
||||||
|
|
||||||
// CheckpointDeleteOptions holds parameters to delete a checkpoint from a container
|
// ImagePullOptions holds information to pull images.
|
||||||
//
|
//
|
||||||
// Deprecated: use [checkpoint.DeleteOptions].
|
// Deprecated: use [image.PullOptions].
|
||||||
type CheckpointDeleteOptions = checkpoint.DeleteOptions |
type ImagePullOptions = image.PullOptions |
||||||
|
|
||||||
// Checkpoint represents the details of a checkpoint when listing endpoints.
|
// ImagePushOptions holds information to push images.
|
||||||
//
|
//
|
||||||
// Deprecated: use [checkpoint.Summary].
|
// Deprecated: use [image.PushOptions].
|
||||||
type Checkpoint = checkpoint.Summary |
type ImagePushOptions = image.PushOptions |
||||||
|
|
||||||
// Info contains response of Engine API:
|
// ImageListOptions holds parameters to list images with.
|
||||||
// GET "/info"
|
|
||||||
//
|
//
|
||||||
// Deprecated: use [system.Info].
|
// Deprecated: use [image.ListOptions].
|
||||||
type Info = system.Info |
type ImageListOptions = image.ListOptions |
||||||
|
|
||||||
// Commit holds the Git-commit (SHA1) that a binary was built from, as reported
|
// ImageRemoveOptions holds parameters to remove images.
|
||||||
// in the version-string of external tools, such as containerd, or runC.
|
|
||||||
//
|
//
|
||||||
// Deprecated: use [system.Commit].
|
// Deprecated: use [image.RemoveOptions].
|
||||||
type Commit = system.Commit |
type ImageRemoveOptions = image.RemoveOptions |
||||||
|
|
||||||
// PluginsInfo is a temp struct holding Plugins name
|
|
||||||
// registered with docker daemon. It is used by [system.Info] struct
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.PluginsInfo].
|
|
||||||
type PluginsInfo = system.PluginsInfo |
|
||||||
|
|
||||||
// NetworkAddressPool is a temp struct used by [system.Info] struct.
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.NetworkAddressPool].
|
|
||||||
type NetworkAddressPool = system.NetworkAddressPool |
|
||||||
|
|
||||||
// Runtime describes an OCI runtime.
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.Runtime].
|
|
||||||
type Runtime = system.Runtime |
|
||||||
|
|
||||||
// SecurityOpt contains the name and options of a security option.
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.SecurityOpt].
|
|
||||||
type SecurityOpt = system.SecurityOpt |
|
||||||
|
|
||||||
// KeyValue holds a key/value pair.
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.KeyValue].
|
|
||||||
type KeyValue = system.KeyValue |
|
||||||
|
|
||||||
// ImageDeleteResponseItem image delete response item.
|
|
||||||
//
|
|
||||||
// Deprecated: use [image.DeleteResponse].
|
|
||||||
type ImageDeleteResponseItem = image.DeleteResponse |
|
||||||
|
|
||||||
// ImageSummary image summary.
|
|
||||||
//
|
|
||||||
// Deprecated: use [image.Summary].
|
|
||||||
type ImageSummary = image.Summary |
|
||||||
|
|
||||||
// ImageMetadata contains engine-local data about the image.
|
|
||||||
//
|
|
||||||
// Deprecated: use [image.Metadata].
|
|
||||||
type ImageMetadata = image.Metadata |
|
||||||
|
|
||||||
// ServiceCreateResponse contains the information returned to a client
|
|
||||||
// on the creation of a new service.
|
|
||||||
//
|
|
||||||
// Deprecated: use [swarm.ServiceCreateResponse].
|
|
||||||
type ServiceCreateResponse = swarm.ServiceCreateResponse |
|
||||||
|
|
||||||
// ServiceUpdateResponse service update response.
|
|
||||||
//
|
|
||||||
// Deprecated: use [swarm.ServiceUpdateResponse].
|
|
||||||
type ServiceUpdateResponse = swarm.ServiceUpdateResponse |
|
||||||
|
|
||||||
// ContainerStartOptions holds parameters to start containers.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.StartOptions].
|
|
||||||
type ContainerStartOptions = container.StartOptions |
|
||||||
|
|
||||||
// ResizeOptions holds parameters to resize a TTY.
|
|
||||||
// It can be used to resize container TTYs and
|
|
||||||
// exec process TTYs too.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.ResizeOptions].
|
|
||||||
type ResizeOptions = container.ResizeOptions |
|
||||||
|
|
||||||
// ContainerAttachOptions holds parameters to attach to a container.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.AttachOptions].
|
|
||||||
type ContainerAttachOptions = container.AttachOptions |
|
||||||
|
|
||||||
// ContainerCommitOptions holds parameters to commit changes into a container.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.CommitOptions].
|
|
||||||
type ContainerCommitOptions = container.CommitOptions |
|
||||||
|
|
||||||
// ContainerListOptions holds parameters to list containers with.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.ListOptions].
|
|
||||||
type ContainerListOptions = container.ListOptions |
|
||||||
|
|
||||||
// ContainerLogsOptions holds parameters to filter logs with.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.LogsOptions].
|
|
||||||
type ContainerLogsOptions = container.LogsOptions |
|
||||||
|
|
||||||
// ContainerRemoveOptions holds parameters to remove containers.
|
|
||||||
//
|
|
||||||
// Deprecated: use [container.RemoveOptions].
|
|
||||||
type ContainerRemoveOptions = container.RemoveOptions |
|
||||||
|
|
||||||
// DecodeSecurityOptions decodes a security options string slice to a type safe
|
|
||||||
// [system.SecurityOpt].
|
|
||||||
//
|
|
||||||
// Deprecated: use [system.DecodeSecurityOptions].
|
|
||||||
func DecodeSecurityOptions(opts []string) ([]system.SecurityOpt, error) { |
|
||||||
return system.DecodeSecurityOptions(opts) |
|
||||||
} |
|
||||||
|
@ -1,14 +0,0 @@ |
|||||||
# Legacy API type versions |
|
||||||
|
|
||||||
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`. |
|
||||||
|
|
||||||
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`. |
|
||||||
|
|
||||||
## Package name conventions |
|
||||||
|
|
||||||
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention: |
|
||||||
|
|
||||||
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`. |
|
||||||
2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`. |
|
||||||
|
|
||||||
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`. |
|
@ -1,8 +0,0 @@ |
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
|
||||||
|
|
||||||
const ( |
|
||||||
envKeyName = "HOME" |
|
||||||
homeShortCut = "~" |
|
||||||
) |
|
@ -1,6 +0,0 @@ |
|||||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
|
||||||
|
|
||||||
const ( |
|
||||||
envKeyName = "USERPROFILE" |
|
||||||
homeShortCut = "%USERPROFILE%" // be careful while using in format functions
|
|
||||||
) |
|
@ -1,19 +0,0 @@ |
|||||||
package system |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"runtime" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// ErrNotSupportedOperatingSystem means the operating system is not supported.
|
|
||||||
//
|
|
||||||
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
|
||||||
var ErrNotSupportedOperatingSystem = errors.New("operating system is not supported") |
|
||||||
|
|
||||||
// IsOSSupported determines if an operating system is supported by the host.
|
|
||||||
//
|
|
||||||
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
|
||||||
func IsOSSupported(os string) bool { |
|
||||||
return strings.EqualFold(runtime.GOOS, os) |
|
||||||
} |
|
@ -1 +1,2 @@ |
|||||||
.idea |
.idea |
||||||
|
coverage.txt |
@ -1,5 +0,0 @@ |
|||||||
ci: |
|
||||||
@goimports -l . || (goimports -d . && exit 1)
|
|
||||||
@golangci-lint run
|
|
||||||
@go test -v .
|
|
||||||
.DEFAULT_GOAL := ci
|
|
@ -0,0 +1,62 @@ |
|||||||
|
# Go package for working with a system's hostsfile |
||||||
|
[![codecov](https://codecov.io/gh/goodhosts/hostsfile/branch/main/graph/badge.svg?token=BJQH16QQEH)](https://codecov.io/gh/goodhosts/hostsfile) |
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/goodhosts/hostsfile.svg)](https://pkg.go.dev/github.com/goodhosts/hostsfile) |
||||||
|
|
||||||
|
Reads the content of a file in the [hosts format](https://en.wikipedia.org/wiki/Hosts_(file)) into go structs for easy manipulation in go programs. When all changes are complete you can `Flush` the hosts file back to disk to save your changes. Supports an indexing system on both ips and hosts for quick management of large hosts files. |
||||||
|
|
||||||
|
## Simple Usage |
||||||
|
Simple usage reading in your system's hosts file and adding an entry for the ip `192.168.1.1` and the host `my-hostname` |
||||||
|
|
||||||
|
```go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
|
||||||
|
"github.com/goodhosts/hostsfile" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
hosts, err := hostsfile.NewHosts() |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err.Error()) |
||||||
|
} |
||||||
|
if err := hosts.Add("192.168.1.1", "my-hostname"); err != nil { |
||||||
|
log.Fatal(err.Error()) |
||||||
|
} |
||||||
|
if err := hosts.Flush(); err != nil { |
||||||
|
log.Fatal(err.Error()) |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### Other Usage |
||||||
|
Read in a hosts file from a custom location which is not the system default, this is useful for tests or systems with non-standard hosts file locations. |
||||||
|
``` |
||||||
|
hosts, err := hostsfile.NewCustomHosts("./my-custom-hostsfile") |
||||||
|
``` |
||||||
|
|
||||||
|
Use `Add` to put an ip and host combination in the hosts file |
||||||
|
``` |
||||||
|
err := hosts.Add("192.168.1.1", "my-hostname") |
||||||
|
``` |
||||||
|
|
||||||
|
`Add` is variadic and can take multiple hosts to add for the same ip |
||||||
|
``` |
||||||
|
err := hosts.Add("192.168.1.1", "my-hostname", "another-hostname") |
||||||
|
``` |
||||||
|
|
||||||
|
Use `Remove` to drop an ip and host combination from the hosts file |
||||||
|
``` |
||||||
|
err := hosts.Remove("192.168.1.1", "my-hostname") |
||||||
|
``` |
||||||
|
|
||||||
|
`Remove` is variadic and can take multiple hosts to remove from the same ip |
||||||
|
``` |
||||||
|
err := hosts.Remove("192.168.1.1", "my-hostname", "another-hostname") |
||||||
|
``` |
||||||
|
|
||||||
|
Flush the hosts file changes back to disk |
||||||
|
``` |
||||||
|
err := hosts.Flush() |
||||||
|
``` |
@ -0,0 +1,6 @@ |
|||||||
|
//go:build test
|
||||||
|
// +build test
|
||||||
|
|
||||||
|
package hostsfile |
||||||
|
|
||||||
|
func main() {} |
@ -0,0 +1,42 @@ |
|||||||
|
package hostsfile |
||||||
|
|
||||||
|
import "sync" |
||||||
|
|
||||||
|
func newLookup() lookup { |
||||||
|
return lookup{l: make(map[string][]int)} |
||||||
|
} |
||||||
|
|
||||||
|
// lookup a rw mutex with a hashmap to keep track of keys (ips/hosts) and their position in the hostsfile
|
||||||
|
type lookup struct { |
||||||
|
sync.RWMutex |
||||||
|
l map[string][]int |
||||||
|
} |
||||||
|
|
||||||
|
func (lo *lookup) add(key string, pos int) { |
||||||
|
lo.Lock() |
||||||
|
defer lo.Unlock() |
||||||
|
lo.l[key] = append(lo.l[key], pos) |
||||||
|
} |
||||||
|
|
||||||
|
func (lo *lookup) remove(key string, pos int) { |
||||||
|
lo.Lock() |
||||||
|
defer lo.Unlock() |
||||||
|
// remove one entry from the lookup because we add one at a time
|
||||||
|
lo.l[key] = removeOneFromSliceInt(pos, lo.l[key]) |
||||||
|
} |
||||||
|
|
||||||
|
func (lo *lookup) get(key string) []int { |
||||||
|
lo.RLock() |
||||||
|
defer lo.RUnlock() |
||||||
|
if i, ok := lo.l[key]; ok { |
||||||
|
return i |
||||||
|
} |
||||||
|
|
||||||
|
return []int{} |
||||||
|
} |
||||||
|
|
||||||
|
func (lo *lookup) reset() { |
||||||
|
lo.Lock() |
||||||
|
defer lo.Unlock() |
||||||
|
lo.l = make(map[string][]int) |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package install |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os/exec" |
||||||
|
|
||||||
|
"github.com/magefile/mage/mg" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
GolangCILintVersion = "v1.54.2" |
||||||
|
) |
||||||
|
|
||||||
|
// Dependencies install all dependencies
|
||||||
|
func Dependencies() error { |
||||||
|
fmt.Println("Installing Dependencies...") |
||||||
|
mg.Deps(Golangcilint) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Golangcilint install golangci-lint
|
||||||
|
func Golangcilint() error { |
||||||
|
fmt.Println("Installing GolangCI Lint...") |
||||||
|
cmd := exec.Command("go", "install", "github.com/golangci/golangci-lint/cmd/golangci-lint@"+GolangCILintVersion) |
||||||
|
return cmd.Run() |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
package test |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/magefile/mage/sh" |
||||||
|
) |
||||||
|
|
||||||
|
// Unit run all unit tests
|
||||||
|
func Unit() error { |
||||||
|
fmt.Println("Running Tests...") |
||||||
|
return sh.RunV("go", "test") |
||||||
|
} |
||||||
|
|
||||||
|
// Build run a test build to confirm no compilation errors
|
||||||
|
func Build() error { |
||||||
|
fmt.Println("Running Build...") |
||||||
|
return sh.RunV("go", "build", "-tags", "test") |
||||||
|
} |
||||||
|
|
||||||
|
// Coverage run all unit tests and output coverage
|
||||||
|
func Coverage() error { |
||||||
|
fmt.Println("Running Tests with Coverage...") |
||||||
|
return sh.RunV("go", "test", "-v", "-coverprofile=coverage.txt", ".") |
||||||
|
} |
||||||
|
|
||||||
|
// HTML display the html coverage report from the cover tool
|
||||||
|
func HTML() error { |
||||||
|
if err := Coverage(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return sh.RunV("go", "tool", "cover", "-html", "coverage.txt") |
||||||
|
} |
||||||
|
|
||||||
|
// Func display the func coverage report from the cover tool
|
||||||
|
func Func() error { |
||||||
|
if err := Coverage(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return sh.RunV("go", "tool", "cover", "-func", "coverage.txt") |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
//go:build mage
|
||||||
|
// +build mage
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/magefile/mage/mg" |
||||||
|
"github.com/magefile/mage/sh" |
||||||
|
|
||||||
|
//mage:import install
|
||||||
|
"github.com/goodhosts/hostsfile/mage/install" |
||||||
|
|
||||||
|
//mage:import test
|
||||||
|
"github.com/goodhosts/hostsfile/mage/test" |
||||||
|
) |
||||||
|
|
||||||
|
// run everything for ci process (install deps, lint, coverage, build)
|
||||||
|
func Ci() error { |
||||||
|
fmt.Println("Running Continuous Integration...") |
||||||
|
mg.Deps( |
||||||
|
install.Dependencies, |
||||||
|
Lint, |
||||||
|
test.Coverage, |
||||||
|
test.Build) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// run the linter
|
||||||
|
func Lint() error { |
||||||
|
mg.Deps(install.Golangcilint) |
||||||
|
fmt.Println("Running Linter...") |
||||||
|
return sh.RunV("golangci-lint", "run") |
||||||
|
} |
@ -1,27 +0,0 @@ |
|||||||
@echo off |
|
||||||
SETLOCAL ENABLEDELAYEDEXPANSION |
|
||||||
set LF=^ |
|
||||||
|
|
||||||
echo %1% |
|
||||||
if "%1%"=="" ( |
|
||||||
set cmd=ci |
|
||||||
) else ( |
|
||||||
set cmd=%1% |
|
||||||
) |
|
||||||
|
|
||||||
if "%cmd%" == "ci" ( |
|
||||||
for /F %%i in ('goimports -l .') do ( |
|
||||||
set "line=%%i" |
|
||||||
set goimports=%goimports%!line!!LF! |
|
||||||
) |
|
||||||
|
|
||||||
if not "!goimports!" == "" ( |
|
||||||
goimports -d . |
|
||||||
goto :eof |
|
||||||
) |
|
||||||
|
|
||||||
golangci-lint run || goto :eof |
|
||||||
go test -v . || goto :eof |
|
||||||
|
|
||||||
goto :eof |
|
||||||
) |
|
@ -0,0 +1,137 @@ |
|||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This is an EXPERIMENTAL package, and may change in arbitrary ways without notice.
|
||||||
|
package layout |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/fs" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// GarbageCollect removes unreferenced blobs from the oci-layout
|
||||||
|
//
|
||||||
|
// This is an experimental api, and not subject to any stability guarantees
|
||||||
|
// We may abandon it at any time, without prior notice.
|
||||||
|
// Deprecated: Use it at your own risk!
|
||||||
|
func (l Path) GarbageCollect() ([]v1.Hash, error) { |
||||||
|
idx, err := l.ImageIndex() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
blobsToKeep := map[string]bool{} |
||||||
|
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
blobsDir := l.path("blobs") |
||||||
|
removedBlobs := []v1.Hash{} |
||||||
|
|
||||||
|
err = filepath.WalkDir(blobsDir, func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if d.IsDir() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
rel, err := filepath.Rel(blobsDir, path) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
hashString := strings.Replace(rel, "/", ":", 1) |
||||||
|
if present := blobsToKeep[hashString]; !present { |
||||||
|
h, err := v1.NewHash(hashString) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
removedBlobs = append(removedBlobs, h) |
||||||
|
} |
||||||
|
return nil |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return removedBlobs, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l Path) garbageCollectImageIndex(index v1.ImageIndex, blobsToKeep map[string]bool) error { |
||||||
|
idxm, err := index.IndexManifest() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
h, err := index.Digest() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
blobsToKeep[h.String()] = true |
||||||
|
|
||||||
|
for _, descriptor := range idxm.Manifests { |
||||||
|
if descriptor.MediaType.IsImage() { |
||||||
|
img, err := index.Image(descriptor.Digest) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := l.garbageCollectImage(img, blobsToKeep); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else if descriptor.MediaType.IsIndex() { |
||||||
|
idx, err := index.ImageIndex(descriptor.Digest) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
return fmt.Errorf("gc: unknown media type: %s", descriptor.MediaType) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l Path) garbageCollectImage(image v1.Image, blobsToKeep map[string]bool) error { |
||||||
|
h, err := image.Digest() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
blobsToKeep[h.String()] = true |
||||||
|
|
||||||
|
h, err = image.ConfigName() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
blobsToKeep[h.String()] = true |
||||||
|
|
||||||
|
ls, err := image.Layers() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
for _, l := range ls { |
||||||
|
h, err := l.Digest() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
blobsToKeep[h.String()] = true |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work. |
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following |
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}" |
||||||
|
replaced with your own identifying information. (Don't include |
||||||
|
the brackets!) The text should be enclosed in the appropriate |
||||||
|
comment syntax for the file format. We also recommend that a |
||||||
|
file or class name and description of purpose be included on the |
||||||
|
same "printed page" as the copyright notice for easier |
||||||
|
identification within third-party archives. |
||||||
|
|
||||||
|
Copyright 2017 the Mage authors |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
@ -0,0 +1,80 @@ |
|||||||
|
package mg |
||||||
|
|
||||||
|
// Color is ANSI color type
|
||||||
|
type Color int |
||||||
|
|
||||||
|
// If you add/change/remove any items in this constant,
|
||||||
|
// you will need to run "stringer -type=Color" in this directory again.
|
||||||
|
// NOTE: Please keep the list in an alphabetical order.
|
||||||
|
const ( |
||||||
|
Black Color = iota |
||||||
|
Red |
||||||
|
Green |
||||||
|
Yellow |
||||||
|
Blue |
||||||
|
Magenta |
||||||
|
Cyan |
||||||
|
White |
||||||
|
BrightBlack |
||||||
|
BrightRed |
||||||
|
BrightGreen |
||||||
|
BrightYellow |
||||||
|
BrightBlue |
||||||
|
BrightMagenta |
||||||
|
BrightCyan |
||||||
|
BrightWhite |
||||||
|
) |
||||||
|
|
||||||
|
// AnsiColor are ANSI color codes for supported terminal colors.
|
||||||
|
var ansiColor = map[Color]string{ |
||||||
|
Black: "\u001b[30m", |
||||||
|
Red: "\u001b[31m", |
||||||
|
Green: "\u001b[32m", |
||||||
|
Yellow: "\u001b[33m", |
||||||
|
Blue: "\u001b[34m", |
||||||
|
Magenta: "\u001b[35m", |
||||||
|
Cyan: "\u001b[36m", |
||||||
|
White: "\u001b[37m", |
||||||
|
BrightBlack: "\u001b[30;1m", |
||||||
|
BrightRed: "\u001b[31;1m", |
||||||
|
BrightGreen: "\u001b[32;1m", |
||||||
|
BrightYellow: "\u001b[33;1m", |
||||||
|
BrightBlue: "\u001b[34;1m", |
||||||
|
BrightMagenta: "\u001b[35;1m", |
||||||
|
BrightCyan: "\u001b[36;1m", |
||||||
|
BrightWhite: "\u001b[37;1m", |
||||||
|
} |
||||||
|
|
||||||
|
// AnsiColorReset is an ANSI color code to reset the terminal color.
|
||||||
|
const AnsiColorReset = "\033[0m" |
||||||
|
|
||||||
|
// DefaultTargetAnsiColor is a default ANSI color for colorizing targets.
|
||||||
|
// It is set to Cyan as an arbitrary color, because it has a neutral meaning
|
||||||
|
var DefaultTargetAnsiColor = ansiColor[Cyan] |
||||||
|
|
||||||
|
func toLowerCase(s string) string { |
||||||
|
// this is a naive implementation
|
||||||
|
// borrowed from https://golang.org/src/strings/strings.go
|
||||||
|
// and only considers alphabetical characters [a-zA-Z]
|
||||||
|
// so that we don't depend on the "strings" package
|
||||||
|
buf := make([]byte, len(s)) |
||||||
|
for i := 0; i < len(s); i++ { |
||||||
|
c := s[i] |
||||||
|
if 'A' <= c && c <= 'Z' { |
||||||
|
c += 'a' - 'A' |
||||||
|
} |
||||||
|
buf[i] = c |
||||||
|
} |
||||||
|
return string(buf) |
||||||
|
} |
||||||
|
|
||||||
|
func getAnsiColor(color string) (string, bool) { |
||||||
|
colorLower := toLowerCase(color) |
||||||
|
for k, v := range ansiColor { |
||||||
|
colorConstLower := toLowerCase(k.String()) |
||||||
|
if colorConstLower == colorLower { |
||||||
|
return v, true |
||||||
|
} |
||||||
|
} |
||||||
|
return "", false |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
// Code generated by "stringer -type=Color"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package mg |
||||||
|
|
||||||
|
import "strconv" |
||||||
|
|
||||||
|
func _() { |
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{} |
||||||
|
_ = x[Black-0] |
||||||
|
_ = x[Red-1] |
||||||
|
_ = x[Green-2] |
||||||
|
_ = x[Yellow-3] |
||||||
|
_ = x[Blue-4] |
||||||
|
_ = x[Magenta-5] |
||||||
|
_ = x[Cyan-6] |
||||||
|
_ = x[White-7] |
||||||
|
_ = x[BrightBlack-8] |
||||||
|
_ = x[BrightRed-9] |
||||||
|
_ = x[BrightGreen-10] |
||||||
|
_ = x[BrightYellow-11] |
||||||
|
_ = x[BrightBlue-12] |
||||||
|
_ = x[BrightMagenta-13] |
||||||
|
_ = x[BrightCyan-14] |
||||||
|
_ = x[BrightWhite-15] |
||||||
|
} |
||||||
|
|
||||||
|
const _Color_name = "BlackRedGreenYellowBlueMagentaCyanWhiteBrightBlackBrightRedBrightGreenBrightYellowBrightBlueBrightMagentaBrightCyanBrightWhite" |
||||||
|
|
||||||
|
var _Color_index = [...]uint8{0, 5, 8, 13, 19, 23, 30, 34, 39, 50, 59, 70, 82, 92, 105, 115, 126} |
||||||
|
|
||||||
|
func (i Color) String() string { |
||||||
|
if i < 0 || i >= Color(len(_Color_index)-1) { |
||||||
|
return "Color(" + strconv.FormatInt(int64(i), 10) + ")" |
||||||
|
} |
||||||
|
return _Color_name[_Color_index[i]:_Color_index[i+1]] |
||||||
|
} |
@ -0,0 +1,211 @@ |
|||||||
|
package mg |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"runtime" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
var logger = log.New(os.Stderr, "", 0) |
||||||
|
|
||||||
|
type onceMap struct { |
||||||
|
mu *sync.Mutex |
||||||
|
m map[onceKey]*onceFun |
||||||
|
} |
||||||
|
|
||||||
|
type onceKey struct { |
||||||
|
Name string |
||||||
|
ID string |
||||||
|
} |
||||||
|
|
||||||
|
func (o *onceMap) LoadOrStore(f Fn) *onceFun { |
||||||
|
defer o.mu.Unlock() |
||||||
|
o.mu.Lock() |
||||||
|
|
||||||
|
key := onceKey{ |
||||||
|
Name: f.Name(), |
||||||
|
ID: f.ID(), |
||||||
|
} |
||||||
|
existing, ok := o.m[key] |
||||||
|
if ok { |
||||||
|
return existing |
||||||
|
} |
||||||
|
one := &onceFun{ |
||||||
|
once: &sync.Once{}, |
||||||
|
fn: f, |
||||||
|
displayName: displayName(f.Name()), |
||||||
|
} |
||||||
|
o.m[key] = one |
||||||
|
return one |
||||||
|
} |
||||||
|
|
||||||
|
var onces = &onceMap{ |
||||||
|
mu: &sync.Mutex{}, |
||||||
|
m: map[onceKey]*onceFun{}, |
||||||
|
} |
||||||
|
|
||||||
|
// SerialDeps is like Deps except it runs each dependency serially, instead of
|
||||||
|
// in parallel. This can be useful for resource intensive dependencies that
|
||||||
|
// shouldn't be run at the same time.
|
||||||
|
func SerialDeps(fns ...interface{}) { |
||||||
|
funcs := checkFns(fns) |
||||||
|
ctx := context.Background() |
||||||
|
for i := range fns { |
||||||
|
runDeps(ctx, funcs[i:i+1]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// SerialCtxDeps is like CtxDeps except it runs each dependency serially,
|
||||||
|
// instead of in parallel. This can be useful for resource intensive
|
||||||
|
// dependencies that shouldn't be run at the same time.
|
||||||
|
func SerialCtxDeps(ctx context.Context, fns ...interface{}) { |
||||||
|
funcs := checkFns(fns) |
||||||
|
for i := range fns { |
||||||
|
runDeps(ctx, funcs[i:i+1]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// CtxDeps runs the given functions as dependencies of the calling function.
|
||||||
|
// Dependencies must only be of type:
|
||||||
|
// func()
|
||||||
|
// func() error
|
||||||
|
// func(context.Context)
|
||||||
|
// func(context.Context) error
|
||||||
|
// Or a similar method on a mg.Namespace type.
|
||||||
|
// Or an mg.Fn interface.
|
||||||
|
//
|
||||||
|
// The function calling Deps is guaranteed that all dependent functions will be
|
||||||
|
// run exactly once when Deps returns. Dependent functions may in turn declare
|
||||||
|
// their own dependencies using Deps. Each dependency is run in their own
|
||||||
|
// goroutines. Each function is given the context provided if the function
|
||||||
|
// prototype allows for it.
|
||||||
|
func CtxDeps(ctx context.Context, fns ...interface{}) { |
||||||
|
funcs := checkFns(fns) |
||||||
|
runDeps(ctx, funcs) |
||||||
|
} |
||||||
|
|
||||||
|
// runDeps assumes you've already called checkFns.
|
||||||
|
func runDeps(ctx context.Context, fns []Fn) { |
||||||
|
mu := &sync.Mutex{} |
||||||
|
var errs []string |
||||||
|
var exit int |
||||||
|
wg := &sync.WaitGroup{} |
||||||
|
for _, f := range fns { |
||||||
|
fn := onces.LoadOrStore(f) |
||||||
|
wg.Add(1) |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
if v := recover(); v != nil { |
||||||
|
mu.Lock() |
||||||
|
if err, ok := v.(error); ok { |
||||||
|
exit = changeExit(exit, ExitStatus(err)) |
||||||
|
} else { |
||||||
|
exit = changeExit(exit, 1) |
||||||
|
} |
||||||
|
errs = append(errs, fmt.Sprint(v)) |
||||||
|
mu.Unlock() |
||||||
|
} |
||||||
|
wg.Done() |
||||||
|
}() |
||||||
|
if err := fn.run(ctx); err != nil { |
||||||
|
mu.Lock() |
||||||
|
errs = append(errs, fmt.Sprint(err)) |
||||||
|
exit = changeExit(exit, ExitStatus(err)) |
||||||
|
mu.Unlock() |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
wg.Wait() |
||||||
|
if len(errs) > 0 { |
||||||
|
panic(Fatal(exit, strings.Join(errs, "\n"))) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func checkFns(fns []interface{}) []Fn { |
||||||
|
funcs := make([]Fn, len(fns)) |
||||||
|
for i, f := range fns { |
||||||
|
if fn, ok := f.(Fn); ok { |
||||||
|
funcs[i] = fn |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Check if the target provided is a not function so we can give a clear warning
|
||||||
|
t := reflect.TypeOf(f) |
||||||
|
if t == nil || t.Kind() != reflect.Func { |
||||||
|
panic(fmt.Errorf("non-function used as a target dependency: %T. The mg.Deps, mg.SerialDeps and mg.CtxDeps functions accept function names, such as mg.Deps(TargetA, TargetB)", f)) |
||||||
|
} |
||||||
|
|
||||||
|
funcs[i] = F(f) |
||||||
|
} |
||||||
|
return funcs |
||||||
|
} |
||||||
|
|
||||||
|
// Deps runs the given functions in parallel, exactly once. Dependencies must
|
||||||
|
// only be of type:
|
||||||
|
// func()
|
||||||
|
// func() error
|
||||||
|
// func(context.Context)
|
||||||
|
// func(context.Context) error
|
||||||
|
// Or a similar method on a mg.Namespace type.
|
||||||
|
// Or an mg.Fn interface.
|
||||||
|
//
|
||||||
|
// This is a way to build up a tree of dependencies with each dependency
|
||||||
|
// defining its own dependencies. Functions must have the same signature as a
|
||||||
|
// Mage target, i.e. optional context argument, optional error return.
|
||||||
|
func Deps(fns ...interface{}) { |
||||||
|
CtxDeps(context.Background(), fns...) |
||||||
|
} |
||||||
|
|
||||||
|
func changeExit(old, new int) int { |
||||||
|
if new == 0 { |
||||||
|
return old |
||||||
|
} |
||||||
|
if old == 0 { |
||||||
|
return new |
||||||
|
} |
||||||
|
if old == new { |
||||||
|
return old |
||||||
|
} |
||||||
|
// both different and both non-zero, just set
|
||||||
|
// exit to 1. Nothing more we can do.
|
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
// funcName returns the unique name for the function
|
||||||
|
func funcName(i interface{}) string { |
||||||
|
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() |
||||||
|
} |
||||||
|
|
||||||
|
func displayName(name string) string { |
||||||
|
splitByPackage := strings.Split(name, ".") |
||||||
|
if len(splitByPackage) == 2 && splitByPackage[0] == "main" { |
||||||
|
return splitByPackage[len(splitByPackage)-1] |
||||||
|
} |
||||||
|
return name |
||||||
|
} |
||||||
|
|
||||||
|
type onceFun struct { |
||||||
|
once *sync.Once |
||||||
|
fn Fn |
||||||
|
err error |
||||||
|
|
||||||
|
displayName string |
||||||
|
} |
||||||
|
|
||||||
|
// run will run the function exactly once and capture the error output. Further runs simply return
|
||||||
|
// the same error output.
|
||||||
|
func (o *onceFun) run(ctx context.Context) error { |
||||||
|
o.once.Do(func() { |
||||||
|
if Verbose() { |
||||||
|
logger.Println("Running dependency:", displayName(o.fn.Name())) |
||||||
|
} |
||||||
|
o.err = o.fn.Run(ctx) |
||||||
|
}) |
||||||
|
return o.err |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
package mg |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type fatalErr struct { |
||||||
|
code int |
||||||
|
error |
||||||
|
} |
||||||
|
|
||||||
|
func (f fatalErr) ExitStatus() int { |
||||||
|
return f.code |
||||||
|
} |
||||||
|
|
||||||
|
type exitStatus interface { |
||||||
|
ExitStatus() int |
||||||
|
} |
||||||
|
|
||||||
|
// Fatal returns an error that will cause mage to print out the
|
||||||
|
// given args and exit with the given exit code.
|
||||||
|
func Fatal(code int, args ...interface{}) error { |
||||||
|
return fatalErr{ |
||||||
|
code: code, |
||||||
|
error: errors.New(fmt.Sprint(args...)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Fatalf returns an error that will cause mage to print out the
|
||||||
|
// given message and exit with the given exit code.
|
||||||
|
func Fatalf(code int, format string, args ...interface{}) error { |
||||||
|
return fatalErr{ |
||||||
|
code: code, |
||||||
|
error: fmt.Errorf(format, args...), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ExitStatus queries the error for an exit status. If the error is nil, it
|
||||||
|
// returns 0. If the error does not implement ExitStatus() int, it returns 1.
|
||||||
|
// Otherwise it retiurns the value from ExitStatus().
|
||||||
|
func ExitStatus(err error) int { |
||||||
|
if err == nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
exit, ok := err.(exitStatus) |
||||||
|
if !ok { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
return exit.ExitStatus() |
||||||
|
} |
@ -0,0 +1,192 @@ |
|||||||
|
package mg |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// Fn represents a function that can be run with mg.Deps. Package, Name, and ID must combine to
|
||||||
|
// uniquely identify a function, while ensuring the "same" function has identical values. These are
|
||||||
|
// used as a map key to find and run (or not run) the function.
|
||||||
|
type Fn interface { |
||||||
|
// Name should return the fully qualified name of the function. Usually
|
||||||
|
// it's best to use runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name().
|
||||||
|
Name() string |
||||||
|
|
||||||
|
// ID should be an additional uniqueness qualifier in case the name is insufficiently unique.
|
||||||
|
// This can be the case for functions that take arguments (mg.F json-encodes an array of the
|
||||||
|
// args).
|
||||||
|
ID() string |
||||||
|
|
||||||
|
// Run should run the function.
|
||||||
|
Run(ctx context.Context) error |
||||||
|
} |
||||||
|
|
||||||
|
// F takes a function that is compatible as a mage target, and any args that need to be passed to
|
||||||
|
// it, and wraps it in an mg.Fn that mg.Deps can run. Args must be passed in the same order as they
|
||||||
|
// are declared by the function. Note that you do not need to and should not pass a context.Context
|
||||||
|
// to F, even if the target takes a context. Compatible args are int, bool, string, and
|
||||||
|
// time.Duration.
|
||||||
|
func F(target interface{}, args ...interface{}) Fn { |
||||||
|
hasContext, isNamespace, err := checkF(target, args) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
id, err := json.Marshal(args) |
||||||
|
if err != nil { |
||||||
|
panic(fmt.Errorf("can't convert args into a mage-compatible id for mg.Deps: %s", err)) |
||||||
|
} |
||||||
|
return fn{ |
||||||
|
name: funcName(target), |
||||||
|
id: string(id), |
||||||
|
f: func(ctx context.Context) error { |
||||||
|
v := reflect.ValueOf(target) |
||||||
|
count := len(args) |
||||||
|
if hasContext { |
||||||
|
count++ |
||||||
|
} |
||||||
|
if isNamespace { |
||||||
|
count++ |
||||||
|
} |
||||||
|
vargs := make([]reflect.Value, count) |
||||||
|
x := 0 |
||||||
|
if isNamespace { |
||||||
|
vargs[0] = reflect.ValueOf(struct{}{}) |
||||||
|
x++ |
||||||
|
} |
||||||
|
if hasContext { |
||||||
|
vargs[x] = reflect.ValueOf(ctx) |
||||||
|
x++ |
||||||
|
} |
||||||
|
for y := range args { |
||||||
|
vargs[x+y] = reflect.ValueOf(args[y]) |
||||||
|
} |
||||||
|
ret := v.Call(vargs) |
||||||
|
if len(ret) > 0 { |
||||||
|
// we only allow functions with a single error return, so this should be safe.
|
||||||
|
if ret[0].IsNil() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return ret[0].Interface().(error) |
||||||
|
} |
||||||
|
return nil |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type fn struct { |
||||||
|
name string |
||||||
|
id string |
||||||
|
f func(ctx context.Context) error |
||||||
|
} |
||||||
|
|
||||||
|
// Name returns the fully qualified name of the function.
|
||||||
|
func (f fn) Name() string { |
||||||
|
return f.name |
||||||
|
} |
||||||
|
|
||||||
|
// ID returns a hash of the argument values passed in
|
||||||
|
func (f fn) ID() string { |
||||||
|
return f.id |
||||||
|
} |
||||||
|
|
||||||
|
// Run runs the function.
|
||||||
|
func (f fn) Run(ctx context.Context) error { |
||||||
|
return f.f(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
func checkF(target interface{}, args []interface{}) (hasContext, isNamespace bool, _ error) { |
||||||
|
t := reflect.TypeOf(target) |
||||||
|
if t == nil || t.Kind() != reflect.Func { |
||||||
|
return false, false, fmt.Errorf("non-function passed to mg.F: %T. The mg.F function accepts function names, such as mg.F(TargetA, \"arg1\", \"arg2\")", target) |
||||||
|
} |
||||||
|
|
||||||
|
if t.NumOut() > 1 { |
||||||
|
return false, false, fmt.Errorf("target has too many return values, must be zero or just an error: %T", target) |
||||||
|
} |
||||||
|
if t.NumOut() == 1 && t.Out(0) != errType { |
||||||
|
return false, false, fmt.Errorf("target's return value is not an error") |
||||||
|
} |
||||||
|
|
||||||
|
// more inputs than slots is an error if not variadic
|
||||||
|
if len(args) > t.NumIn() && !t.IsVariadic() { |
||||||
|
return false, false, fmt.Errorf("too many arguments for target, got %d for %T", len(args), target) |
||||||
|
} |
||||||
|
|
||||||
|
if t.NumIn() == 0 { |
||||||
|
return false, false, nil |
||||||
|
} |
||||||
|
|
||||||
|
x := 0 |
||||||
|
inputs := t.NumIn() |
||||||
|
|
||||||
|
if t.In(0).AssignableTo(emptyType) { |
||||||
|
// nameSpace func
|
||||||
|
isNamespace = true |
||||||
|
x++ |
||||||
|
// callers must leave off the namespace value
|
||||||
|
inputs-- |
||||||
|
} |
||||||
|
if t.NumIn() > x && t.In(x) == ctxType { |
||||||
|
// callers must leave off the context
|
||||||
|
inputs-- |
||||||
|
|
||||||
|
// let the upper function know it should pass us a context.
|
||||||
|
hasContext = true |
||||||
|
|
||||||
|
// skip checking the first argument in the below loop if it's a context, since first arg is
|
||||||
|
// special.
|
||||||
|
x++ |
||||||
|
} |
||||||
|
|
||||||
|
if t.IsVariadic() { |
||||||
|
if len(args) < inputs-1 { |
||||||
|
return false, false, fmt.Errorf("too few arguments for target, got %d for %T", len(args), target) |
||||||
|
|
||||||
|
} |
||||||
|
} else if len(args) != inputs { |
||||||
|
return false, false, fmt.Errorf("wrong number of arguments for target, got %d for %T", len(args), target) |
||||||
|
} |
||||||
|
|
||||||
|
for _, arg := range args { |
||||||
|
argT := t.In(x) |
||||||
|
if t.IsVariadic() && x == t.NumIn()-1 { |
||||||
|
// For the variadic argument, use the slice element type.
|
||||||
|
argT = argT.Elem() |
||||||
|
} |
||||||
|
if !argTypes[argT] { |
||||||
|
return false, false, fmt.Errorf("argument %d (%s), is not a supported argument type", x, argT) |
||||||
|
} |
||||||
|
passedT := reflect.TypeOf(arg) |
||||||
|
if argT != passedT { |
||||||
|
return false, false, fmt.Errorf("argument %d expected to be %s, but is %s", x, argT, passedT) |
||||||
|
} |
||||||
|
if x < t.NumIn()-1 { |
||||||
|
x++ |
||||||
|
} |
||||||
|
} |
||||||
|
return hasContext, isNamespace, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Here we define the types that are supported as arguments/returns
|
||||||
|
var ( |
||||||
|
ctxType = reflect.TypeOf(func(context.Context) {}).In(0) |
||||||
|
errType = reflect.TypeOf(func() error { return nil }).Out(0) |
||||||
|
emptyType = reflect.TypeOf(struct{}{}) |
||||||
|
|
||||||
|
intType = reflect.TypeOf(int(0)) |
||||||
|
stringType = reflect.TypeOf(string("")) |
||||||
|
boolType = reflect.TypeOf(bool(false)) |
||||||
|
durType = reflect.TypeOf(time.Second) |
||||||
|
|
||||||
|
// don't put ctx in here, this is for non-context types
|
||||||
|
argTypes = map[reflect.Type]bool{ |
||||||
|
intType: true, |
||||||
|
boolType: true, |
||||||
|
stringType: true, |
||||||
|
durType: true, |
||||||
|
} |
||||||
|
) |
@ -0,0 +1,136 @@ |
|||||||
|
package mg |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"runtime" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// CacheEnv is the environment variable that users may set to change the
|
||||||
|
// location where mage stores its compiled binaries.
|
||||||
|
const CacheEnv = "MAGEFILE_CACHE" |
||||||
|
|
||||||
|
// VerboseEnv is the environment variable that indicates the user requested
|
||||||
|
// verbose mode when running a magefile.
|
||||||
|
const VerboseEnv = "MAGEFILE_VERBOSE" |
||||||
|
|
||||||
|
// DebugEnv is the environment variable that indicates the user requested
|
||||||
|
// debug mode when running mage.
|
||||||
|
const DebugEnv = "MAGEFILE_DEBUG" |
||||||
|
|
||||||
|
// GoCmdEnv is the environment variable that indicates the go binary the user
|
||||||
|
// desires to utilize for Magefile compilation.
|
||||||
|
const GoCmdEnv = "MAGEFILE_GOCMD" |
||||||
|
|
||||||
|
// IgnoreDefaultEnv is the environment variable that indicates the user requested
|
||||||
|
// to ignore the default target specified in the magefile.
|
||||||
|
const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT" |
||||||
|
|
||||||
|
// HashFastEnv is the environment variable that indicates the user requested to
|
||||||
|
// use a quick hash of magefiles to determine whether or not the magefile binary
|
||||||
|
// needs to be rebuilt. This results in faster runtimes, but means that mage
|
||||||
|
// will fail to rebuild if a dependency has changed. To force a rebuild, run
|
||||||
|
// mage with the -f flag.
|
||||||
|
const HashFastEnv = "MAGEFILE_HASHFAST" |
||||||
|
|
||||||
|
// EnableColorEnv is the environment variable that indicates the user is using
|
||||||
|
// a terminal which supports a color output. The default is false for backwards
|
||||||
|
// compatibility. When the value is true and the detected terminal does support colors
|
||||||
|
// then the list of mage targets will be displayed in ANSI color. When the value
|
||||||
|
// is true but the detected terminal does not support colors, then the list of
|
||||||
|
// mage targets will be displayed in the default colors (e.g. black and white).
|
||||||
|
const EnableColorEnv = "MAGEFILE_ENABLE_COLOR" |
||||||
|
|
||||||
|
// TargetColorEnv is the environment variable that indicates which ANSI color
|
||||||
|
// should be used to colorize mage targets. This is only applicable when
|
||||||
|
// the MAGEFILE_ENABLE_COLOR environment variable is true.
|
||||||
|
// The supported ANSI color names are any of these:
|
||||||
|
// - Black
|
||||||
|
// - Red
|
||||||
|
// - Green
|
||||||
|
// - Yellow
|
||||||
|
// - Blue
|
||||||
|
// - Magenta
|
||||||
|
// - Cyan
|
||||||
|
// - White
|
||||||
|
// - BrightBlack
|
||||||
|
// - BrightRed
|
||||||
|
// - BrightGreen
|
||||||
|
// - BrightYellow
|
||||||
|
// - BrightBlue
|
||||||
|
// - BrightMagenta
|
||||||
|
// - BrightCyan
|
||||||
|
// - BrightWhite
|
||||||
|
const TargetColorEnv = "MAGEFILE_TARGET_COLOR" |
||||||
|
|
||||||
|
// Verbose reports whether a magefile was run with the verbose flag.
|
||||||
|
func Verbose() bool { |
||||||
|
b, _ := strconv.ParseBool(os.Getenv(VerboseEnv)) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// Debug reports whether a magefile was run with the debug flag.
|
||||||
|
func Debug() bool { |
||||||
|
b, _ := strconv.ParseBool(os.Getenv(DebugEnv)) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// GoCmd reports the command that Mage will use to build go code. By default mage runs
|
||||||
|
// the "go" binary in the PATH.
|
||||||
|
func GoCmd() string { |
||||||
|
if cmd := os.Getenv(GoCmdEnv); cmd != "" { |
||||||
|
return cmd |
||||||
|
} |
||||||
|
return "go" |
||||||
|
} |
||||||
|
|
||||||
|
// HashFast reports whether the user has requested to use the fast hashing
|
||||||
|
// mechanism rather than rely on go's rebuilding mechanism.
|
||||||
|
func HashFast() bool { |
||||||
|
b, _ := strconv.ParseBool(os.Getenv(HashFastEnv)) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// IgnoreDefault reports whether the user has requested to ignore the default target
|
||||||
|
// in the magefile.
|
||||||
|
func IgnoreDefault() bool { |
||||||
|
b, _ := strconv.ParseBool(os.Getenv(IgnoreDefaultEnv)) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// CacheDir returns the directory where mage caches compiled binaries. It
|
||||||
|
// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE
|
||||||
|
// environment variable.
|
||||||
|
func CacheDir() string { |
||||||
|
d := os.Getenv(CacheEnv) |
||||||
|
if d != "" { |
||||||
|
return d |
||||||
|
} |
||||||
|
switch runtime.GOOS { |
||||||
|
case "windows": |
||||||
|
return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), "magefile") |
||||||
|
default: |
||||||
|
return filepath.Join(os.Getenv("HOME"), ".magefile") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// EnableColor reports whether the user has requested to enable a color output.
|
||||||
|
func EnableColor() bool { |
||||||
|
b, _ := strconv.ParseBool(os.Getenv(EnableColorEnv)) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// TargetColor returns the configured ANSI color name a color output.
|
||||||
|
func TargetColor() string { |
||||||
|
s, exists := os.LookupEnv(TargetColorEnv) |
||||||
|
if exists { |
||||||
|
if c, ok := getAnsiColor(s); ok { |
||||||
|
return c |
||||||
|
} |
||||||
|
} |
||||||
|
return DefaultTargetAnsiColor |
||||||
|
} |
||||||
|
|
||||||
|
// Namespace allows for the grouping of similar commands
|
||||||
|
type Namespace struct{} |
@ -0,0 +1,184 @@ |
|||||||
|
package sh |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/magefile/mage/mg" |
||||||
|
) |
||||||
|
|
||||||
|
// RunCmd returns a function that will call Run with the given command. This is
|
||||||
|
// useful for creating command aliases to make your scripts easier to read, like
|
||||||
|
// this:
|
||||||
|
//
|
||||||
|
// // in a helper file somewhere
|
||||||
|
// var g0 = sh.RunCmd("go") // go is a keyword :(
|
||||||
|
//
|
||||||
|
// // somewhere in your main code
|
||||||
|
// if err := g0("install", "github.com/gohugo/hugo"); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Args passed to command get baked in as args to the command when you run it.
|
||||||
|
// Any args passed in when you run the returned function will be appended to the
|
||||||
|
// original args. For example, this is equivalent to the above:
|
||||||
|
//
|
||||||
|
// var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
|
||||||
|
//
|
||||||
|
// RunCmd uses Exec underneath, so see those docs for more details.
|
||||||
|
func RunCmd(cmd string, args ...string) func(args ...string) error { |
||||||
|
return func(args2 ...string) error { |
||||||
|
return Run(cmd, append(args, args2...)...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// OutCmd is like RunCmd except the command returns the output of the
|
||||||
|
// command.
|
||||||
|
func OutCmd(cmd string, args ...string) func(args ...string) (string, error) { |
||||||
|
return func(args2 ...string) (string, error) { |
||||||
|
return Output(cmd, append(args, args2...)...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Run is like RunWith, but doesn't specify any environment variables.
|
||||||
|
func Run(cmd string, args ...string) error { |
||||||
|
return RunWith(nil, cmd, args...) |
||||||
|
} |
||||||
|
|
||||||
|
// RunV is like Run, but always sends the command's stdout to os.Stdout.
|
||||||
|
func RunV(cmd string, args ...string) error { |
||||||
|
_, err := Exec(nil, os.Stdout, os.Stderr, cmd, args...) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// RunWith runs the given command, directing stderr to this program's stderr and
|
||||||
|
// printing stdout to stdout if mage was run with -v. It adds adds env to the
|
||||||
|
// environment variables for the command being run. Environment variables should
|
||||||
|
// be in the format name=value.
|
||||||
|
func RunWith(env map[string]string, cmd string, args ...string) error { |
||||||
|
var output io.Writer |
||||||
|
if mg.Verbose() { |
||||||
|
output = os.Stdout |
||||||
|
} |
||||||
|
_, err := Exec(env, output, os.Stderr, cmd, args...) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// RunWithV is like RunWith, but always sends the command's stdout to os.Stdout.
|
||||||
|
func RunWithV(env map[string]string, cmd string, args ...string) error { |
||||||
|
_, err := Exec(env, os.Stdout, os.Stderr, cmd, args...) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Output runs the command and returns the text from stdout.
|
||||||
|
func Output(cmd string, args ...string) (string, error) { |
||||||
|
buf := &bytes.Buffer{} |
||||||
|
_, err := Exec(nil, buf, os.Stderr, cmd, args...) |
||||||
|
return strings.TrimSuffix(buf.String(), "\n"), err |
||||||
|
} |
||||||
|
|
||||||
|
// OutputWith is like RunWith, but returns what is written to stdout.
|
||||||
|
func OutputWith(env map[string]string, cmd string, args ...string) (string, error) { |
||||||
|
buf := &bytes.Buffer{} |
||||||
|
_, err := Exec(env, buf, os.Stderr, cmd, args...) |
||||||
|
return strings.TrimSuffix(buf.String(), "\n"), err |
||||||
|
} |
||||||
|
|
||||||
|
// Exec executes the command, piping its stdout and stderr to the given
|
||||||
|
// writers. If the command fails, it will return an error that, if returned
|
||||||
|
// from a target or mg.Deps call, will cause mage to exit with the same code as
|
||||||
|
// the command failed with. Env is a list of environment variables to set when
|
||||||
|
// running the command, these override the current environment variables set
|
||||||
|
// (which are also passed to the command). cmd and args may include references
|
||||||
|
// to environment variables in $FOO format, in which case these will be
|
||||||
|
// expanded before the command is run.
|
||||||
|
//
|
||||||
|
// Ran reports if the command ran (rather than was not found or not executable).
|
||||||
|
// Code reports the exit code the command returned if it ran. If err == nil, ran
|
||||||
|
// is always true and code is always 0.
|
||||||
|
func Exec(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error) { |
||||||
|
expand := func(s string) string { |
||||||
|
s2, ok := env[s] |
||||||
|
if ok { |
||||||
|
return s2 |
||||||
|
} |
||||||
|
return os.Getenv(s) |
||||||
|
} |
||||||
|
cmd = os.Expand(cmd, expand) |
||||||
|
for i := range args { |
||||||
|
args[i] = os.Expand(args[i], expand) |
||||||
|
} |
||||||
|
ran, code, err := run(env, stdout, stderr, cmd, args...) |
||||||
|
if err == nil { |
||||||
|
return true, nil |
||||||
|
} |
||||||
|
if ran { |
||||||
|
return ran, mg.Fatalf(code, `running "%s %s" failed with exit code %d`, cmd, strings.Join(args, " "), code) |
||||||
|
} |
||||||
|
return ran, fmt.Errorf(`failed to run "%s %s: %v"`, cmd, strings.Join(args, " "), err) |
||||||
|
} |
||||||
|
|
||||||
|
func run(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, code int, err error) { |
||||||
|
c := exec.Command(cmd, args...) |
||||||
|
c.Env = os.Environ() |
||||||
|
for k, v := range env { |
||||||
|
c.Env = append(c.Env, k+"="+v) |
||||||
|
} |
||||||
|
c.Stderr = stderr |
||||||
|
c.Stdout = stdout |
||||||
|
c.Stdin = os.Stdin |
||||||
|
|
||||||
|
var quoted []string
|
||||||
|
for i := range args { |
||||||
|
quoted = append(quoted, fmt.Sprintf("%q", args[i])); |
||||||
|
} |
||||||
|
// To protect against logging from doing exec in global variables
|
||||||
|
if mg.Verbose() { |
||||||
|
log.Println("exec:", cmd, strings.Join(quoted, " ")) |
||||||
|
} |
||||||
|
err = c.Run() |
||||||
|
return CmdRan(err), ExitStatus(err), err |
||||||
|
} |
||||||
|
// CmdRan examines the error to determine if it was generated as a result of a
|
||||||
|
// command running via os/exec.Command. If the error is nil, or the command ran
|
||||||
|
// (even if it exited with a non-zero exit code), CmdRan reports true. If the
|
||||||
|
// error is an unrecognized type, or it is an error from exec.Command that says
|
||||||
|
// the command failed to run (usually due to the command not existing or not
|
||||||
|
// being executable), it reports false.
|
||||||
|
func CmdRan(err error) bool { |
||||||
|
if err == nil { |
||||||
|
return true |
||||||
|
} |
||||||
|
ee, ok := err.(*exec.ExitError) |
||||||
|
if ok { |
||||||
|
return ee.Exited() |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
type exitStatus interface { |
||||||
|
ExitStatus() int |
||||||
|
} |
||||||
|
|
||||||
|
// ExitStatus returns the exit status of the error if it is an exec.ExitError
|
||||||
|
// or if it implements ExitStatus() int.
|
||||||
|
// 0 if it is nil or 1 if it is a different error.
|
||||||
|
func ExitStatus(err error) int { |
||||||
|
if err == nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
if e, ok := err.(exitStatus); ok { |
||||||
|
return e.ExitStatus() |
||||||
|
} |
||||||
|
if e, ok := err.(*exec.ExitError); ok { |
||||||
|
if ex, ok := e.Sys().(exitStatus); ok { |
||||||
|
return ex.ExitStatus() |
||||||
|
} |
||||||
|
} |
||||||
|
return 1 |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
package sh |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
// Rm removes the given file or directory even if non-empty. It will not return
|
||||||
|
// an error if the target doesn't exist, only if the target cannot be removed.
|
||||||
|
func Rm(path string) error { |
||||||
|
err := os.RemoveAll(path) |
||||||
|
if err == nil || os.IsNotExist(err) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return fmt.Errorf(`failed to remove %s: %v`, path, err) |
||||||
|
} |
||||||
|
|
||||||
|
// Copy robustly copies the source file to the destination, overwriting the destination if necessary.
|
||||||
|
func Copy(dst string, src string) error { |
||||||
|
from, err := os.Open(src) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf(`can't copy %s: %v`, src, err) |
||||||
|
} |
||||||
|
defer from.Close() |
||||||
|
finfo, err := from.Stat() |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf(`can't stat %s: %v`, src, err) |
||||||
|
} |
||||||
|
to, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, finfo.Mode()) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf(`can't copy to %s: %v`, dst, err) |
||||||
|
} |
||||||
|
defer to.Close() |
||||||
|
_, err = io.Copy(to, from) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf(`error copying %s to %s: %v`, src, dst, err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work. |
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following |
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||||
|
replaced with your own identifying information. (Don't include |
||||||
|
the brackets!) The text should be enclosed in the appropriate |
||||||
|
comment syntax for the file format. We also recommend that a |
||||||
|
file or class name and description of purpose be included on the |
||||||
|
same "printed page" as the copyright notice for easier |
||||||
|
identification within third-party archives. |
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner] |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
Loading…
Reference in new issue