Cameradar 3.0.0: Uses ullaakut/nmap, runs faster, removed legacy code (#188)
Unit tests functional and coverage back to 100% Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
This commit is contained in:
committed by
GitHub
parent
878ca9f032
commit
5849898283
+2
@@ -0,0 +1,2 @@
|
||||
site/* linguist-documentation
|
||||
vendor/* linguist-vendored
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Magefile output
|
||||
mage_output_file.go
|
||||
|
||||
# VScode
|
||||
.vscode
|
||||
|
||||
# stupid osx
|
||||
.DS_Store
|
||||
|
||||
# Goland
|
||||
.idea
|
||||
*.iml
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
project_name: mage
|
||||
release:
|
||||
github:
|
||||
owner: magefile
|
||||
name: mage
|
||||
draft: true
|
||||
build:
|
||||
binary: mage
|
||||
main: .
|
||||
ldflags: -s -w -X github.com/magefile/mage/mage.timestamp={{.Date}} -X github.com/magefile/mage/mage.commitHash={{.Commit}} -X github.com/magefile/mage/mage.gitTag={{.Version}}
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
- netbsd
|
||||
- openbsd
|
||||
- dragonfly
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
archive:
|
||||
name_template: "{{.Binary}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
language: go
|
||||
|
||||
# prevent double test runs for PRs
|
||||
branches:
|
||||
only:
|
||||
- "master"
|
||||
|
||||
# In theory, older versions would probably work fine, but since this isn't a
|
||||
# library, I'm not going to worry about older versions for now.
|
||||
go:
|
||||
- tip
|
||||
- 1.11.x
|
||||
- 1.10.x
|
||||
- 1.9.x
|
||||
- 1.8.x
|
||||
- 1.7.x
|
||||
|
||||
# don't call go get ./... because this hides when deps are
|
||||
# not packaged into the vendor directory.
|
||||
install: true
|
||||
|
||||
# don't call go test -v because we want to be able to only show t.Log output when
|
||||
# a test fails
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -tags CI -race ./...
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# Contributing
|
||||
|
||||
Of course, contributions are more than welcome. Please read these guidelines for
|
||||
making the process as painless as possible.
|
||||
|
||||
## Discussion
|
||||
|
||||
Development discussion should take place on the #mage channel of [gopher
|
||||
slack](https://gophers.slack.com/).
|
||||
|
||||
There is a separate #mage-dev channel that has the github app to post github
|
||||
activity to the channel, to make it easy to follow.
|
||||
|
||||
## Issues
|
||||
|
||||
If there's an issue you'd like to work on, please comment on it, so we can
|
||||
discuss approach, etc. and make sure no one else is currently working on that
|
||||
issue.
|
||||
|
||||
Please always create an issue before sending a PR unless it's an obvious typo
|
||||
or other trivial change.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
Currently mage has no dependencies(!) outside the standard libary. Let's keep
|
||||
it that way. Since it's likely that mage will be vendored into a project,
|
||||
adding dependencies to mage adds dependencies to every project that uses mage.
|
||||
|
||||
## Versions
|
||||
|
||||
Please avoid using features of go and the stdlib that prevent mage from being
|
||||
buildable with older versions of Go. The CI tests currently check that mage is
|
||||
buildable with go 1.7 and later. You may build with whatever version you like,
|
||||
but CI has the final say.
|
||||
|
||||
## Testing
|
||||
|
||||
Please write tests for any new features. Tests must use the normal go testing
|
||||
package.
|
||||
|
||||
Tests must pass the race detector (run `go test -race ./...`).
|
||||
|
||||
+201
@@ -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.
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
[](https://magefile.org)
|
||||
[](https://travis-ci.org/magefile/mage) [](https://ci.appveyor.com/project/natefinch/mage/branch/master)
|
||||
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/3185864/32058716-5ee9b512-ba38-11e7-978a-287eb2a62743.png"/></p>
|
||||
|
||||
## About
|
||||
|
||||
Mage is a make-like build tool using Go. You write plain-old go functions,
|
||||
and Mage automatically uses them as Makefile-like runnable targets.
|
||||
|
||||
## Installation
|
||||
|
||||
Mage has no dependencies outside the Go standard library, and builds with Go 1.7
|
||||
and above (possibly even lower versions, but they're not regularly tested).
|
||||
|
||||
**Using GOPATH**
|
||||
|
||||
```
|
||||
go get -u -d github.com/magefile/mage
|
||||
cd $GOPATH/src/github.com/magefile/mage
|
||||
go run bootstrap.go
|
||||
```
|
||||
|
||||
**Using Go Modules**
|
||||
|
||||
```
|
||||
git clone https://github.com:magefile/mage
|
||||
cd mage
|
||||
go run bootstrap.go
|
||||
```
|
||||
|
||||
This will download the code and then run the bootstrap script to build mage with
|
||||
version infomation embedded in it. A normal `go get` (without -d) or `go
|
||||
install` will build the binary correctly, but no version info will be embedded.
|
||||
If you've done this, no worries, just go to `$GOPATH/src/github.com/magefile/mage`
|
||||
and run `mage install` or `go run bootstrap.go` and a new binary will be created
|
||||
with the correct version information.
|
||||
|
||||
The mage binary will be created in your $GOPATH/bin directory.
|
||||
|
||||
You may also install a binary release from our
|
||||
[releases](https://github.com/magefile/mage/releases) page.
|
||||
|
||||
## Demo
|
||||
|
||||
[](https://www.youtube.com/watch?v=GOqbD0lF-iA)
|
||||
|
||||
## Discussion
|
||||
|
||||
Join the `#mage` channel on [gophers slack](https://gophers.slack.com/messages/general/)
|
||||
or post on the [magefile google group](https://groups.google.com/forum/#!forum/magefile)
|
||||
for discussion of usage, development, etc.
|
||||
|
||||
# Documentation
|
||||
|
||||
see [magefile.org](https://magefile.org) for full docs
|
||||
|
||||
see [godoc.org/github.com/magefile/mage/mage](https://godoc.org/github.com/magefile/mage/mage) for how to use mage as a library.
|
||||
|
||||
# Why?
|
||||
|
||||
Makefiles are hard to read and hard to write. Mostly because makefiles are
|
||||
essentially fancy bash scripts with significant white space and additional
|
||||
make-related syntax.
|
||||
|
||||
Mage lets you have multiple magefiles, name your magefiles whatever you want,
|
||||
and they're easy to customize for multiple operating systems. Mage has no
|
||||
dependencies (aside from go) and runs just fine on all major operating systems,
|
||||
whereas make generally uses bash which is not well supported on Windows. Go is
|
||||
superior to bash for any non-trivial task involving branching, looping, anything
|
||||
that's not just straight line execution of commands. And if your project is
|
||||
written in Go, why introduce another language as idiosyncratic as bash? Why not
|
||||
use the language your contributors are already comfortable with?
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//+build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/magefile/mage/mage"
|
||||
)
|
||||
|
||||
// This is a bootstrap builder, to build mage when you don't already *have* mage.
|
||||
// Run it like
|
||||
// go run bootstrap.go
|
||||
// and it will install mage with all the right flags created for you.
|
||||
|
||||
func main() {
|
||||
os.Args = []string{os.Args[0], "-v", "install"}
|
||||
os.Exit(mage.Main())
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
module github.com/magefile/mage
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
//+build CI
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
s, err := run("go", "run", "bootstrap.go")
|
||||
if err != nil {
|
||||
t.Fatal(s)
|
||||
}
|
||||
name := "mage"
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(os.Getenv("GOPATH"), "bin", name)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(cmd string, args ...string) (string, error) {
|
||||
c := exec.Command(cmd, args...)
|
||||
c.Env = os.Environ()
|
||||
b, err := c.CombinedOutput()
|
||||
return string(b), err
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//+build mage
|
||||
|
||||
// This is the build script for Mage. The install target is all you really need.
|
||||
// The release target is for generating offial releases and is really only
|
||||
// useful to project admins.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/magefile/mage/mg"
|
||||
"github.com/magefile/mage/sh"
|
||||
)
|
||||
|
||||
// Runs "go install" for mage. This generates the version info the binary.
|
||||
func Install() error {
|
||||
name := "mage"
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
|
||||
gocmd := mg.GoCmd()
|
||||
gopath, err := sh.Output(gocmd, "env", "GOPATH")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't determine GOPATH: %v", err)
|
||||
}
|
||||
paths := strings.Split(gopath, string([]rune{os.PathListSeparator}))
|
||||
bin := filepath.Join(paths[0], "bin")
|
||||
// specifically don't mkdirall, if you have an invalid gopath in the first
|
||||
// place, that's not on us to fix.
|
||||
if err := os.Mkdir(bin, 0700); err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("failed to create %q: %v", bin, err)
|
||||
}
|
||||
path := filepath.Join(bin, name)
|
||||
|
||||
// we use go build here because if someone built with go get, then `go
|
||||
// install` turns into a no-op, and `go install -a` fails on people's
|
||||
// machines that have go installed in a non-writeable directory (such as
|
||||
// normal OS installs in /usr/bin)
|
||||
return sh.RunV(gocmd, "build", "-o", path, "-ldflags="+flags(), "github.com/magefile/mage")
|
||||
}
|
||||
|
||||
var releaseTag = regexp.MustCompile(`^v1\.[0-9]+\.[0-9]+$`)
|
||||
|
||||
// Generates a new release. Expects the TAG environment variable to be set,
|
||||
// which will create a new tag with that name.
|
||||
func Release() (err error) {
|
||||
tag := os.Getenv("TAG")
|
||||
if !releaseTag.MatchString(tag) {
|
||||
return errors.New("TAG environment variable must be in semver v1.x.x format, but was " + tag)
|
||||
}
|
||||
|
||||
if err := sh.RunV("git", "tag", "-a", tag, "-m", tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sh.RunV("git", "push", "origin", tag); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
sh.RunV("git", "tag", "--delete", "$TAG")
|
||||
sh.RunV("git", "push", "--delete", "origin", "$TAG")
|
||||
}
|
||||
}()
|
||||
return sh.RunV("goreleaser")
|
||||
}
|
||||
|
||||
// Remove the temporarily generated files from Release.
|
||||
func Clean() error {
|
||||
return sh.Rm("dist")
|
||||
}
|
||||
|
||||
func flags() string {
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
hash := hash()
|
||||
tag := tag()
|
||||
if tag == "" {
|
||||
tag = "dev"
|
||||
}
|
||||
return fmt.Sprintf(`-X "github.com/magefile/mage/mage.timestamp=%s" -X "github.com/magefile/mage/mage.commitHash=%s" -X "github.com/magefile/mage/mage.gitTag=%s"`, timestamp, hash, tag)
|
||||
}
|
||||
|
||||
// tag returns the git tag for the current branch or "" if none.
|
||||
func tag() string {
|
||||
s, _ := sh.Output("git", "describe", "--tags")
|
||||
return s
|
||||
}
|
||||
|
||||
// hash returns the git hash for the current repo or "" if none.
|
||||
func hash() string {
|
||||
hash, _ := sh.Output("git", "rev-parse", "--short", "HEAD")
|
||||
return hash
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/magefile/mage/mage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(mage.Main())
|
||||
}
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
package mg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// funcType indicates a prototype of build job function
|
||||
type funcType int
|
||||
|
||||
// funcTypes
|
||||
const (
|
||||
invalidType funcType = iota
|
||||
voidType
|
||||
errorType
|
||||
contextVoidType
|
||||
contextErrorType
|
||||
namespaceVoidType
|
||||
namespaceErrorType
|
||||
namespaceContextVoidType
|
||||
namespaceContextErrorType
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stderr, "", 0)
|
||||
|
||||
type onceMap struct {
|
||||
mu *sync.Mutex
|
||||
m map[string]*onceFun
|
||||
}
|
||||
|
||||
func (o *onceMap) LoadOrStore(s string, one *onceFun) *onceFun {
|
||||
defer o.mu.Unlock()
|
||||
o.mu.Lock()
|
||||
|
||||
existing, ok := o.m[s]
|
||||
if ok {
|
||||
return existing
|
||||
}
|
||||
o.m[s] = one
|
||||
return one
|
||||
}
|
||||
|
||||
var onces = &onceMap{
|
||||
mu: &sync.Mutex{},
|
||||
m: map[string]*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{}) {
|
||||
types := checkFns(fns)
|
||||
ctx := context.Background()
|
||||
for i := range fns {
|
||||
runDeps(ctx, types[i:i+1], fns[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{}) {
|
||||
types := checkFns(fns)
|
||||
for i := range fns {
|
||||
runDeps(ctx, types[i:i+1], fns[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.
|
||||
//
|
||||
// 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{}) {
|
||||
types := checkFns(fns)
|
||||
runDeps(ctx, types, fns)
|
||||
}
|
||||
|
||||
// runDeps assumes you've already called checkFns.
|
||||
func runDeps(ctx context.Context, types []funcType, fns []interface{}) {
|
||||
mu := &sync.Mutex{}
|
||||
var errs []string
|
||||
var exit int
|
||||
wg := &sync.WaitGroup{}
|
||||
for i, f := range fns {
|
||||
fn := addDep(ctx, types[i], 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(); 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{}) []funcType {
|
||||
types := make([]funcType, len(fns))
|
||||
for i, f := range fns {
|
||||
t, err := funcCheck(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
types[i] = t
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
func addDep(ctx context.Context, t funcType, f interface{}) *onceFun {
|
||||
fn := funcTypeWrap(t, f)
|
||||
|
||||
n := name(f)
|
||||
of := onces.LoadOrStore(n, &onceFun{
|
||||
fn: fn,
|
||||
ctx: ctx,
|
||||
|
||||
displayName: displayName(n),
|
||||
})
|
||||
return of
|
||||
}
|
||||
|
||||
func name(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 func(context.Context) error
|
||||
ctx context.Context
|
||||
err error
|
||||
|
||||
displayName string
|
||||
}
|
||||
|
||||
func (o *onceFun) run() error {
|
||||
o.once.Do(func() {
|
||||
if Verbose() {
|
||||
logger.Println("Running dependency:", o.displayName)
|
||||
}
|
||||
o.err = o.fn(o.ctx)
|
||||
})
|
||||
return o.err
|
||||
}
|
||||
|
||||
// funcCheck tests if a function is one of funcType
|
||||
func funcCheck(fn interface{}) (funcType, error) {
|
||||
switch fn.(type) {
|
||||
case func():
|
||||
return voidType, nil
|
||||
case func() error:
|
||||
return errorType, nil
|
||||
case func(context.Context):
|
||||
return contextVoidType, nil
|
||||
case func(context.Context) error:
|
||||
return contextErrorType, nil
|
||||
}
|
||||
|
||||
err := fmt.Errorf("Invalid type for dependent function: %T. Dependencies must be func(), func() error, func(context.Context), func(context.Context) error, or the same method on an mg.Namespace.", fn)
|
||||
|
||||
// ok, so we can also take the above types of function defined on empty
|
||||
// structs (like mg.Namespace). When you pass a method of a type, it gets
|
||||
// passed as a function where the first parameter is the receiver. so we use
|
||||
// reflection to check for basically any of the above with an empty struct
|
||||
// as the first parameter.
|
||||
|
||||
t := reflect.TypeOf(fn)
|
||||
if t.Kind() != reflect.Func {
|
||||
return invalidType, err
|
||||
}
|
||||
|
||||
if t.NumOut() > 1 {
|
||||
return invalidType, err
|
||||
}
|
||||
if t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(err) {
|
||||
return invalidType, err
|
||||
}
|
||||
|
||||
// 1 or 2 argumments, either just the struct, or struct and context.
|
||||
if t.NumIn() == 0 || t.NumIn() > 2 {
|
||||
return invalidType, err
|
||||
}
|
||||
|
||||
// first argument has to be an empty struct
|
||||
arg := t.In(0)
|
||||
if arg.Kind() != reflect.Struct {
|
||||
return invalidType, err
|
||||
}
|
||||
if arg.NumField() != 0 {
|
||||
return invalidType, err
|
||||
}
|
||||
if t.NumIn() == 1 {
|
||||
if t.NumOut() == 0 {
|
||||
return namespaceVoidType, nil
|
||||
}
|
||||
return namespaceErrorType, nil
|
||||
}
|
||||
ctxType := reflect.TypeOf(context.Background())
|
||||
if t.In(1) == ctxType {
|
||||
return invalidType, err
|
||||
}
|
||||
|
||||
if t.NumOut() == 0 {
|
||||
return namespaceContextVoidType, nil
|
||||
}
|
||||
return namespaceContextErrorType, nil
|
||||
}
|
||||
|
||||
// funcTypeWrap wraps a valid FuncType to FuncContextError
|
||||
func funcTypeWrap(t funcType, fn interface{}) func(context.Context) error {
|
||||
switch f := fn.(type) {
|
||||
case func():
|
||||
return func(context.Context) error {
|
||||
f()
|
||||
return nil
|
||||
}
|
||||
case func() error:
|
||||
return func(context.Context) error {
|
||||
return f()
|
||||
}
|
||||
case func(context.Context):
|
||||
return func(ctx context.Context) error {
|
||||
f(ctx)
|
||||
return nil
|
||||
}
|
||||
case func(context.Context) error:
|
||||
return f
|
||||
}
|
||||
args := []reflect.Value{reflect.ValueOf(struct{}{})}
|
||||
switch t {
|
||||
case namespaceVoidType:
|
||||
return func(context.Context) error {
|
||||
v := reflect.ValueOf(fn)
|
||||
v.Call(args)
|
||||
return nil
|
||||
}
|
||||
case namespaceErrorType:
|
||||
return func(context.Context) error {
|
||||
v := reflect.ValueOf(fn)
|
||||
ret := v.Call(args)
|
||||
val := ret[0].Interface()
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.(error)
|
||||
}
|
||||
case namespaceContextVoidType:
|
||||
return func(ctx context.Context) error {
|
||||
v := reflect.ValueOf(fn)
|
||||
v.Call(append(args, reflect.ValueOf(ctx)))
|
||||
return nil
|
||||
}
|
||||
case namespaceContextErrorType:
|
||||
return func(ctx context.Context) error {
|
||||
v := reflect.ValueOf(fn)
|
||||
ret := v.Call(append(args, reflect.ValueOf(ctx)))
|
||||
val := ret[0].Interface()
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.(error)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("Don't know how to deal with dep of type %T", fn))
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package mg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDepsLogging(t *testing.T) {
|
||||
os.Setenv("MAGEFILE_VERBOSE", "1")
|
||||
defer os.Unsetenv("MAGEFILE_VERBOSE")
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
defaultLogger := logger
|
||||
logger = log.New(buf, "", 0)
|
||||
defer func() { logger = defaultLogger }()
|
||||
|
||||
foo()
|
||||
|
||||
if strings.Count(buf.String(), "Running dependency: github.com/magefile/mage/mg.baz") != 1 {
|
||||
t.Fatalf("expected one baz to be logged, but got\n%s", buf)
|
||||
}
|
||||
}
|
||||
|
||||
func foo() {
|
||||
Deps(bar, baz)
|
||||
}
|
||||
|
||||
func bar() {
|
||||
Deps(baz)
|
||||
}
|
||||
|
||||
func baz() {}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package mg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDepsRunOnce(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
f := func() {
|
||||
done <- struct{}{}
|
||||
}
|
||||
go Deps(f, f)
|
||||
select {
|
||||
case <-done:
|
||||
// cool
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
t.Fatal("func not run in a reasonable amount of time.")
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatal("func run twice!")
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
// cool... this should be plenty of time for the goroutine to have run
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepsOfDeps(t *testing.T) {
|
||||
ch := make(chan string, 3)
|
||||
// this->f->g->h
|
||||
h := func() {
|
||||
ch <- "h"
|
||||
}
|
||||
g := func() {
|
||||
Deps(h)
|
||||
ch <- "g"
|
||||
}
|
||||
f := func() {
|
||||
Deps(g)
|
||||
ch <- "f"
|
||||
}
|
||||
Deps(f)
|
||||
|
||||
res := <-ch + <-ch + <-ch
|
||||
|
||||
if res != "hgf" {
|
||||
t.Fatal("expected h then g then f to run, but got " + res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialDeps(t *testing.T) {
|
||||
ch := make(chan string, 3)
|
||||
// this->f->g->h
|
||||
h := func() {
|
||||
ch <- "h"
|
||||
}
|
||||
g := func() {
|
||||
ch <- "g"
|
||||
}
|
||||
f := func() {
|
||||
SerialDeps(g, h)
|
||||
ch <- "f"
|
||||
}
|
||||
Deps(f)
|
||||
|
||||
res := <-ch + <-ch + <-ch
|
||||
|
||||
if res != "ghf" {
|
||||
t.Fatal("expected g then h then f to run, but got " + res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepError(t *testing.T) {
|
||||
// TODO: this test is ugly and relies on implementation details. It should
|
||||
// be recreated as a full-stack test.
|
||||
|
||||
f := func() error {
|
||||
return errors.New("ouch!")
|
||||
}
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Fatal("expected panic, but didn't get one")
|
||||
}
|
||||
actual := fmt.Sprint(err)
|
||||
if "ouch!" != actual {
|
||||
t.Fatalf(`expected to get "ouch!" but got "%s"`, actual)
|
||||
}
|
||||
}()
|
||||
Deps(f)
|
||||
}
|
||||
|
||||
func TestDepFatal(t *testing.T) {
|
||||
f := func() error {
|
||||
return Fatal(99, "ouch!")
|
||||
}
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v == nil {
|
||||
t.Fatal("expected panic, but didn't get one")
|
||||
}
|
||||
actual := fmt.Sprint(v)
|
||||
if "ouch!" != actual {
|
||||
t.Fatalf(`expected to get "ouch!" but got "%s"`, actual)
|
||||
}
|
||||
err, ok := v.(error)
|
||||
if !ok {
|
||||
t.Fatalf("expected recovered val to be error but was %T", v)
|
||||
}
|
||||
code := ExitStatus(err)
|
||||
if code != 99 {
|
||||
t.Fatalf("Expected exit status 99, but got %v", code)
|
||||
}
|
||||
}()
|
||||
Deps(f)
|
||||
}
|
||||
|
||||
func TestDepTwoFatal(t *testing.T) {
|
||||
f := func() error {
|
||||
return Fatal(99, "ouch!")
|
||||
}
|
||||
g := func() error {
|
||||
return Fatal(11, "bang!")
|
||||
}
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v == nil {
|
||||
t.Fatal("expected panic, but didn't get one")
|
||||
}
|
||||
actual := fmt.Sprint(v)
|
||||
// order is non-deterministic, so check for both orders
|
||||
if "ouch!\nbang!" != actual && "bang!\nouch!" != actual {
|
||||
t.Fatalf(`expected to get "ouch!" and "bang!" but got "%s"`, actual)
|
||||
}
|
||||
err, ok := v.(error)
|
||||
if !ok {
|
||||
t.Fatalf("expected recovered val to be error but was %T", v)
|
||||
}
|
||||
code := ExitStatus(err)
|
||||
// two different error codes returns, so we give up and just use error
|
||||
// code 1.
|
||||
if code != 1 {
|
||||
t.Fatalf("Expected exit status 1, but got %v", code)
|
||||
}
|
||||
}()
|
||||
Deps(f, g)
|
||||
}
|
||||
|
||||
func TestDepWithUnhandledFunc(t *testing.T) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
_, ok := err.(error)
|
||||
if !ok {
|
||||
t.Fatalf("Expected type error from panic")
|
||||
}
|
||||
}()
|
||||
var NotValid func(string) string = func(a string) string {
|
||||
return a
|
||||
}
|
||||
Deps(NotValid)
|
||||
}
|
||||
|
||||
func TestDepsErrors(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
log := log.New(buf, "", 0)
|
||||
|
||||
h := func() error {
|
||||
log.Println("running h")
|
||||
return errors.New("oops")
|
||||
}
|
||||
g := func() {
|
||||
Deps(h)
|
||||
log.Println("running g")
|
||||
}
|
||||
f := func() {
|
||||
Deps(g, h)
|
||||
log.Println("running f")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Fatal("expected f to panic")
|
||||
}
|
||||
if buf.String() != "running h\n" {
|
||||
t.Fatalf("expected just h to run, but got\n%s", buf.String())
|
||||
}
|
||||
}()
|
||||
f()
|
||||
}
|
||||
+51
@@ -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()
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package mg
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFatalExit(t *testing.T) {
|
||||
expected := 99
|
||||
code := ExitStatus(Fatal(expected))
|
||||
if code != expected {
|
||||
t.Fatalf("Expected code %v but got %v", expected, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFatalfExit(t *testing.T) {
|
||||
expected := 99
|
||||
code := ExitStatus(Fatalf(expected, "boo!"))
|
||||
if code != expected {
|
||||
t.Fatalf("Expected code %v but got %v", expected, code)
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package mg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncCheck(t *testing.T) {
|
||||
// we can ignore errors here, since the error is always the same
|
||||
// and the FuncType will be InvalidType if there's an error.
|
||||
f, _ := funcCheck(func() {})
|
||||
if f != voidType {
|
||||
t.Errorf("expected func() to be a valid VoidType, but was %v", f)
|
||||
}
|
||||
f, _ = funcCheck(func() error { return nil })
|
||||
if f != errorType {
|
||||
t.Errorf("expected func() error to be a valid ErrorType, but was %v", f)
|
||||
}
|
||||
f, _ = funcCheck(func(context.Context) {})
|
||||
if f != contextVoidType {
|
||||
t.Errorf("expected func(context.Context) to be a valid ContextVoidType, but was %v", f)
|
||||
}
|
||||
f, _ = funcCheck(func(context.Context) error { return nil })
|
||||
if f != contextErrorType {
|
||||
t.Errorf("expected func(context.Context) error to be a valid ContextErrorType but was %v", f)
|
||||
}
|
||||
|
||||
f, _ = funcCheck(Foo.Bare)
|
||||
if f != namespaceVoidType {
|
||||
t.Errorf("expected Foo.Bare to be a valid NamespaceVoidType but was %v", f)
|
||||
}
|
||||
|
||||
f, _ = funcCheck(Foo.Error)
|
||||
if f != namespaceErrorType {
|
||||
t.Errorf("expected Foo.Error to be a valid NamespaceErrorType but was %v", f)
|
||||
}
|
||||
f, _ = funcCheck(Foo.BareCtx)
|
||||
if f != namespaceContextVoidType {
|
||||
t.Errorf("expected Foo.BareCtx to be a valid NamespaceContextVoidType but was %v", f)
|
||||
}
|
||||
f, _ = funcCheck(Foo.CtxError)
|
||||
if f != namespaceContextErrorType {
|
||||
t.Errorf("expected Foo.CtxError to be a valid NamespaceContextErrorType but was %v", f)
|
||||
}
|
||||
|
||||
// Test the Invalid case
|
||||
f, err := funcCheck(func(int) error { return nil })
|
||||
if f != invalidType {
|
||||
t.Errorf("expected func(int) error to be InvalidType but was %v", f)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("expected func(int) error to not be a valid FuncType, but got nil error.")
|
||||
}
|
||||
}
|
||||
|
||||
type Foo Namespace
|
||||
|
||||
func (Foo) Bare() {}
|
||||
|
||||
func (Foo) Error() error { return nil }
|
||||
|
||||
func (Foo) BareCtx(context.Context) {}
|
||||
|
||||
func (Foo) CtxError(context.Context) error { return nil }
|
||||
|
||||
func TestFuncTypeWrap(t *testing.T) {
|
||||
func() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Fatal("Expected a panic, but didn't get one")
|
||||
}
|
||||
}()
|
||||
if funcTypeWrap(voidType, func(i int) {}) != nil {
|
||||
t.Errorf("expected func(int) to return nil")
|
||||
}
|
||||
}()
|
||||
|
||||
if funcTypeWrap(voidType, func() {}) == nil {
|
||||
t.Errorf("expected func() to return a function")
|
||||
}
|
||||
|
||||
if funcTypeWrap(errorType, func() error { return nil }) == nil {
|
||||
t.Errorf("expected func() error to return a function")
|
||||
}
|
||||
|
||||
if funcTypeWrap(contextVoidType, func(context.Context) {}) == nil {
|
||||
t.Errorf("expected func(context.Context) to return a function")
|
||||
}
|
||||
|
||||
if funcTypeWrap(contextErrorType, func(context.Context) error { return nil }) == nil {
|
||||
t.Errorf("expected func(context.Context) error to return a function")
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
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 user requested
|
||||
// verbose mode when running a magefile.
|
||||
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"
|
||||
|
||||
// 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 verbose 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"
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace allows for the grouping of similar commands
|
||||
type Namespace struct{}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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, ubt 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 stderr to mage's stderr and
|
||||
// piping its stdout to the given writer. 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
|
||||
log.Println("exec:", cmd, strings.Join(args, " "))
|
||||
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
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package sh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutCmd(t *testing.T) {
|
||||
cmd := OutCmd(os.Args[0], "-printArgs", "foo", "bar")
|
||||
out, err := cmd("baz", "bat")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "[foo bar baz bat]"
|
||||
if out != expected {
|
||||
t.Fatalf("expected %q but got %q", expected, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCode(t *testing.T) {
|
||||
ran, err := Exec(nil, nil, nil, os.Args[0], "-helper", "-exit", "99")
|
||||
if err == nil {
|
||||
t.Fatal("unexpected nil error from run")
|
||||
}
|
||||
if !ran {
|
||||
t.Errorf("ran returned as false, but should have been true")
|
||||
}
|
||||
code := ExitStatus(err)
|
||||
if code != 99 {
|
||||
t.Fatalf("expected exit status 99, but got %v", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
env := "SOME_REALLY_LONG_MAGEFILE_SPECIFIC_THING"
|
||||
out := &bytes.Buffer{}
|
||||
ran, err := Exec(map[string]string{env: "foobar"}, out, nil, os.Args[0], "-printVar", env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from runner: %#v", err)
|
||||
}
|
||||
if !ran {
|
||||
t.Errorf("expected ran to be true but was false.")
|
||||
}
|
||||
if out.String() != "foobar\n" {
|
||||
t.Errorf("expected foobar, got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRun(t *testing.T) {
|
||||
ran, err := Exec(nil, nil, nil, "thiswontwork")
|
||||
if err == nil {
|
||||
t.Fatal("unexpected nil error")
|
||||
}
|
||||
if ran {
|
||||
t.Fatal("expected ran to be false but was true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoExpand(t *testing.T) {
|
||||
if err := os.Setenv("MAGE_FOOBAR", "baz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := Output("echo", "$MAGE_FOOBAR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s != "baz" {
|
||||
t.Fatalf(`Expected "baz" but got %q`, s)
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
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)
|
||||
}
|
||||
_, err = io.Copy(to, from)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error copying %s to %s: %v`, src, dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package sh_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/magefile/mage/sh"
|
||||
)
|
||||
|
||||
// compareFiles checks that two files are identical for testing purposes. That means they have the same length,
|
||||
// the same contents, and the same permissions. It does NOT mean they have the same timestamp, as that is expected
|
||||
// to change in normal Mage sh.Copy operation.
|
||||
func compareFiles(file1 string, file2 string) error {
|
||||
s1, err := os.Stat(file1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't stat %s: %v", file1, err)
|
||||
}
|
||||
s2, err := os.Stat(file2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't stat %s: %v", file2, err)
|
||||
}
|
||||
if s1.Size() != s2.Size() {
|
||||
return fmt.Errorf("files %s and %s have different sizes: %d vs %d", file1, file2, s1.Size(), s2.Size())
|
||||
}
|
||||
if s1.Mode() != s2.Mode() {
|
||||
return fmt.Errorf("files %s and %s have different permissions: %#4o vs %#4o", file1, file2, s1.Mode(), s2.Mode())
|
||||
}
|
||||
f1bytes, err := ioutil.ReadFile(file1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read %s: %v", file1, err)
|
||||
}
|
||||
f2bytes, err := ioutil.ReadFile(file2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read %s: %v", file2, err)
|
||||
}
|
||||
if !bytes.Equal(f1bytes, f2bytes) {
|
||||
return fmt.Errorf("files %s and %s have different contents", file1, file2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHelpers(t *testing.T) {
|
||||
|
||||
mytmpdir, err := ioutil.TempDir("", "mage")
|
||||
if err != nil {
|
||||
t.Fatalf("can't create test directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
derr := os.RemoveAll(mytmpdir)
|
||||
if derr != nil {
|
||||
fmt.Printf("error cleaning up after TestHelpers: %v", derr)
|
||||
}
|
||||
}()
|
||||
srcname := filepath.Join(mytmpdir, "test1.txt")
|
||||
err = ioutil.WriteFile(srcname, []byte("All work and no play makes Jack a dull boy."), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("can't create test file %s: %v", srcname, err)
|
||||
}
|
||||
destname := filepath.Join(mytmpdir, "test2.txt")
|
||||
|
||||
t.Run("sh/copy", func(t *testing.T) {
|
||||
cerr := sh.Copy(destname, srcname)
|
||||
if cerr != nil {
|
||||
t.Errorf("test file copy from %s to %s failed: %v", srcname, destname, cerr)
|
||||
}
|
||||
cerr = compareFiles(srcname, destname)
|
||||
if cerr != nil {
|
||||
t.Errorf("test file copy verification failed: %v", cerr)
|
||||
}
|
||||
})
|
||||
|
||||
// While we've got a temporary directory, test how forgiving sh.Rm is
|
||||
t.Run("sh/rm/ne", func(t *testing.T) {
|
||||
nef := filepath.Join(mytmpdir, "file_not_exist.txt")
|
||||
rerr := sh.Rm(nef)
|
||||
if rerr != nil {
|
||||
t.Errorf("sh.Rm complained when removing nonexistent file %s: %v", nef, rerr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sh/copy/ne", func(t *testing.T) {
|
||||
nef := filepath.Join(mytmpdir, "file_not_exist.txt")
|
||||
nedf := filepath.Join(mytmpdir, "file_not_exist2.txt")
|
||||
cerr := sh.Copy(nedf, nef)
|
||||
if cerr == nil {
|
||||
t.Errorf("sh.Copy succeeded copying nonexistent file %s", nef)
|
||||
}
|
||||
})
|
||||
|
||||
// We test sh.Rm by clearing up our own test files and directories
|
||||
t.Run("sh/rm", func(t *testing.T) {
|
||||
rerr := sh.Rm(destname)
|
||||
if rerr != nil {
|
||||
t.Errorf("failed to remove file %s: %v", destname, rerr)
|
||||
}
|
||||
rerr = sh.Rm(srcname)
|
||||
if rerr != nil {
|
||||
t.Errorf("failed to remove file %s: %v", srcname, rerr)
|
||||
}
|
||||
rerr = sh.Rm(mytmpdir)
|
||||
if rerr != nil {
|
||||
t.Errorf("failed to remove dir %s: %v", mytmpdir, rerr)
|
||||
}
|
||||
_, rerr = os.Stat(mytmpdir)
|
||||
if rerr == nil {
|
||||
t.Errorf("removed dir %s but it's still there?", mytmpdir)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sh/rm/nedir", func(t *testing.T) {
|
||||
rerr := sh.Rm(mytmpdir)
|
||||
if rerr != nil {
|
||||
t.Errorf("sh.Rm complained removing nonexistent dir %s", mytmpdir)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package sh
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
helperCmd bool
|
||||
printArgs bool
|
||||
stderr string
|
||||
stdout string
|
||||
exitCode int
|
||||
printVar string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&helperCmd, "helper", false, "")
|
||||
flag.BoolVar(&printArgs, "printArgs", false, "")
|
||||
flag.StringVar(&stderr, "stderr", "", "")
|
||||
flag.StringVar(&stdout, "stdout", "", "")
|
||||
flag.IntVar(&exitCode, "exit", 0, "")
|
||||
flag.StringVar(&printVar, "printVar", "", "")
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
if printArgs {
|
||||
fmt.Println(flag.Args())
|
||||
return
|
||||
}
|
||||
if printVar != "" {
|
||||
fmt.Println(os.Getenv(printVar))
|
||||
return
|
||||
}
|
||||
|
||||
if helperCmd {
|
||||
fmt.Fprintln(os.Stderr, stderr)
|
||||
fmt.Fprintln(os.Stdout, stdout)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
Reference in New Issue
Block a user