vendor deps
This commit is contained in:
parent
24c7720831
commit
ff2a9e21b5
|
@ -0,0 +1,312 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. "Contributor" means each individual or legal entity that creates, contributes
|
||||||
|
to the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version" means the combination of the Contributions of others
|
||||||
|
(if any) used by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution" means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software" means Source Code Form to which the initial Contributor
|
||||||
|
has attached the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses" means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described in Exhibit
|
||||||
|
B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a Secondary
|
||||||
|
License.
|
||||||
|
|
||||||
|
1.6. "Executable Form" means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work" means a work that combines Covered Software with other
|
||||||
|
material, in a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License" means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable" means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and all of the
|
||||||
|
rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications" means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor means any patent claim(s), including
|
||||||
|
without limitation, method, process, and apparatus claims, in any patent Licensable
|
||||||
|
by such Contributor that would be infringed, but for the grant of the License,
|
||||||
|
by the making, using, selling, offering for sale, having made, import, or
|
||||||
|
transfer of either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License" means either the GNU General Public License, Version
|
||||||
|
2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form" means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your") means an individual or a legal entity exercising rights
|
||||||
|
under this License. For legal entities, "You" includes any entity that controls,
|
||||||
|
is controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, "control" means (a) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or otherwise,
|
||||||
|
or (b) ownership of more than fifty percent (50%) of the outstanding shares
|
||||||
|
or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive
|
||||||
|
license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark) Licensable
|
||||||
|
by such Contributor to use, reproduce, make available, modify, display, perform,
|
||||||
|
distribute, and otherwise exploit its Contributions, either on an unmodified
|
||||||
|
basis, with Modifications, or as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions or
|
||||||
|
its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's modifications
|
||||||
|
of Covered Software, or (ii) the combination of its Contributions with other
|
||||||
|
software (except as part of its Contributor Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the notice
|
||||||
|
requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to distribute
|
||||||
|
the Covered Software under a subsequent version of this License (see Section
|
||||||
|
10.2) or under the terms of a Secondary License (if permitted under the terms
|
||||||
|
of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the rights
|
||||||
|
to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any Modifications
|
||||||
|
that You create or to which You contribute, must be under the terms of this
|
||||||
|
License. You must inform recipients that the Source Code Form of the Covered
|
||||||
|
Software is governed by the terms of this License, and how they can obtain
|
||||||
|
a copy of this License. You may not attempt to alter or restrict the recipients'
|
||||||
|
rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the Executable
|
||||||
|
Form how they can obtain a copy of such Source Code Form by reasonable means
|
||||||
|
in a timely manner, at a charge no more than the cost of distribution to the
|
||||||
|
recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for the
|
||||||
|
Executable Form does not attempt to limit or alter the recipients' rights
|
||||||
|
in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice, provided
|
||||||
|
that You also comply with the requirements of this License for the Covered
|
||||||
|
Software. If the Larger Work is a combination of Covered Software with a work
|
||||||
|
governed by one or more Secondary Licenses, and the Covered Software is not
|
||||||
|
Incompatible With Secondary Licenses, this License permits You to additionally
|
||||||
|
distribute such Covered Software under the terms of such Secondary License(s),
|
||||||
|
so that the recipient of the Larger Work may, at their option, further distribute
|
||||||
|
the Covered Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered Software,
|
||||||
|
except that You may alter any license notices to the extent required to remedy
|
||||||
|
known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support, indemnity
|
||||||
|
or liability obligations to one or more recipients of Covered Software. However,
|
||||||
|
You may do so only on Your own behalf, and not on behalf of any Contributor.
|
||||||
|
You must make it absolutely clear that any such warranty, support, indemnity,
|
||||||
|
or liability obligation is offered by You alone, and You hereby agree to indemnify
|
||||||
|
every Contributor for any liability incurred by such Contributor as a result
|
||||||
|
of warranty, support, indemnity or liability terms You offer. You may include
|
||||||
|
additional disclaimers of warranty and limitations of liability specific to
|
||||||
|
any jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with
|
||||||
|
all distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be sufficiently
|
||||||
|
detailed for a recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if
|
||||||
|
You fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor are
|
||||||
|
reinstated (a) provisionally, unless and until such Contributor explicitly
|
||||||
|
and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor
|
||||||
|
fails to notify You of the non-compliance by some reasonable means prior to
|
||||||
|
60 days after You have come back into compliance. Moreover, Your grants from
|
||||||
|
a particular Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the first
|
||||||
|
time You have received notice of non-compliance with this License from such
|
||||||
|
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||||
|
of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent infringement
|
||||||
|
claim (excluding declaratory judgment actions, counter-claims, and cross-claims)
|
||||||
|
alleging that a Contributor Version directly or indirectly infringes any patent,
|
||||||
|
then the rights granted to You by any and all Contributors for the Covered
|
||||||
|
Software under Section 2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end
|
||||||
|
user license agreements (excluding distributors and resellers) which have
|
||||||
|
been validly granted by You or Your distributors under this License prior
|
||||||
|
to termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an "as is" basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire risk
|
||||||
|
as to the quality and performance of the Covered Software is with You. Should
|
||||||
|
any Covered Software prove defective in any respect, You (not any Contributor)
|
||||||
|
assume the cost of any necessary servicing, repair, or correction. This disclaimer
|
||||||
|
of warranty constitutes an essential part of this License. No use of any Covered
|
||||||
|
Software is authorized under this License except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any character
|
||||||
|
including, without limitation, damages for lost profits, loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all other commercial
|
||||||
|
damages or losses, even if such party shall have been informed of the possibility
|
||||||
|
of such damages. This limitation of liability shall not apply to liability
|
||||||
|
for death or personal injury resulting from such party's negligence to the
|
||||||
|
extent applicable law prohibits such limitation. Some jurisdictions do not
|
||||||
|
allow the exclusion or limitation of incidental or consequential damages,
|
||||||
|
so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts
|
||||||
|
of a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party's ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it enforceable.
|
||||||
|
Any law or regulation which provides that the language of a contract shall
|
||||||
|
be construed against the drafter shall not be used to construe this License
|
||||||
|
against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section 10.3,
|
||||||
|
no one other than the license steward has the right to modify or publish new
|
||||||
|
versions of this License. Each version will be given a distinguishing version
|
||||||
|
number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or under
|
||||||
|
the terms of any subsequent version published by the license steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to create
|
||||||
|
a new license for such software, you may create and use a modified version
|
||||||
|
of this License if you rename the license and remove any references to the
|
||||||
|
name of the license steward (except to note that such modified license differs
|
||||||
|
from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses under the terms of this version of the License, the notice described
|
||||||
|
in Exhibit B of this License must be attached. Exhibit A - Source Code Form
|
||||||
|
License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
||||||
|
one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file,
|
||||||
|
then You may include the notice in a location (such as a LICENSE file in a
|
||||||
|
relevant directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as defined
|
||||||
|
by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,143 @@
|
||||||
|
package gitver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exactVer *regexp.Regexp
|
||||||
|
var gitVer *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// exactly vX.Y.Z (go-compatible semver)
|
||||||
|
exactVer = regexp.MustCompile(`^v\d+\.\d+\.\d+$`)
|
||||||
|
|
||||||
|
// vX.Y.Z-n-g0000000 git post-release, semver prerelease
|
||||||
|
// vX.Y.Z-dirty git post-release, semver prerelease
|
||||||
|
gitVer = regexp.MustCompile(`^(v\d+\.\d+)\.(\d+)(-(\d+))?(-(g[0-9a-f]+))?(-(dirty))?`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Versions struct {
|
||||||
|
Timestamp time.Time
|
||||||
|
Version string
|
||||||
|
Rev string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecAndParse() (*Versions, error) {
|
||||||
|
desc, err := gitDesc()
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rev, err := gitRev()
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ver, err := semVer(desc)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts, err := gitTimestamp(desc)
|
||||||
|
if nil != err {
|
||||||
|
ts = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Versions{
|
||||||
|
Timestamp: ts,
|
||||||
|
Version: ver,
|
||||||
|
Rev: rev,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitDesc() (string, error) {
|
||||||
|
args := strings.Split("git describe --tags --dirty --always", " ")
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
// Don't panic, just carry on
|
||||||
|
//out = []byte("v0.0.0-0-g0000000")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitRev() (string, error) {
|
||||||
|
args := strings.Split("git rev-parse HEAD", " ")
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
return "", fmt.Errorf("\nUnexpected Error\n\n"+
|
||||||
|
"Please open an issue at https://git.rootprojects.org/root/go-gitver/issues/new \n"+
|
||||||
|
"Please include the following:\n\n"+
|
||||||
|
"Command: %s\n"+
|
||||||
|
"Output: %s\n"+
|
||||||
|
"Error: %s\n"+
|
||||||
|
"\nPlease and Thank You.\n\n", strings.Join(args, " "), out, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func semVer(desc string) (string, error) {
|
||||||
|
if exactVer.MatchString(desc) {
|
||||||
|
// v1.0.0
|
||||||
|
return desc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gitVer.MatchString(desc) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// (v1.0).(0)(-(1))(-(g0000000))(-(dirty))
|
||||||
|
vers := gitVer.FindStringSubmatch(desc)
|
||||||
|
patch, err := strconv.Atoi(vers[2])
|
||||||
|
if nil != err {
|
||||||
|
return "", fmt.Errorf("\nUnexpected Error\n\n"+
|
||||||
|
"Please open an issue at https://git.rootprojects.org/root/go-gitver/issues/new \n"+
|
||||||
|
"Please include the following:\n\n"+
|
||||||
|
"git description: %s\n"+
|
||||||
|
"RegExp: %#v\n"+
|
||||||
|
"Error: %s\n"+
|
||||||
|
"\nPlease and Thank You.\n\n", desc, gitVer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.0.1-pre1
|
||||||
|
// v1.0.1-pre1+g0000000
|
||||||
|
// v1.0.1-pre0+dirty
|
||||||
|
// v1.0.1-pre0+g0000000-dirty
|
||||||
|
if "" == vers[4] {
|
||||||
|
vers[4] = "0"
|
||||||
|
}
|
||||||
|
ver := fmt.Sprintf("%s.%d-pre%s", vers[1], patch+1, vers[4])
|
||||||
|
if "" != vers[6] || "dirty" == vers[8] {
|
||||||
|
ver += "+"
|
||||||
|
if "" != vers[6] {
|
||||||
|
ver += vers[6]
|
||||||
|
if "" != vers[8] {
|
||||||
|
ver += "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ver += vers[8]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitTimestamp(desc string) (time.Time, error) {
|
||||||
|
args := []string{
|
||||||
|
"git",
|
||||||
|
"show", desc,
|
||||||
|
"--format=%cd",
|
||||||
|
"--date=format:%Y-%m-%dT%H:%M:%SZ%z",
|
||||||
|
"--no-patch",
|
||||||
|
}
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
// a dirty desc was probably used
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
return time.Parse(time.RFC3339, strings.TrimSpace(string(out)))
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
xversion.go
|
||||||
|
zversion.go
|
||||||
|
|
||||||
|
/go-gitver
|
||||||
|
examples/**.sum
|
||||||
|
|
||||||
|
# ---> Go
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,312 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. "Contributor" means each individual or legal entity that creates, contributes
|
||||||
|
to the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version" means the combination of the Contributions of others
|
||||||
|
(if any) used by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution" means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software" means Source Code Form to which the initial Contributor
|
||||||
|
has attached the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses" means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described in Exhibit
|
||||||
|
B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a Secondary
|
||||||
|
License.
|
||||||
|
|
||||||
|
1.6. "Executable Form" means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work" means a work that combines Covered Software with other
|
||||||
|
material, in a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License" means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable" means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and all of the
|
||||||
|
rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications" means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor means any patent claim(s), including
|
||||||
|
without limitation, method, process, and apparatus claims, in any patent Licensable
|
||||||
|
by such Contributor that would be infringed, but for the grant of the License,
|
||||||
|
by the making, using, selling, offering for sale, having made, import, or
|
||||||
|
transfer of either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License" means either the GNU General Public License, Version
|
||||||
|
2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form" means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your") means an individual or a legal entity exercising rights
|
||||||
|
under this License. For legal entities, "You" includes any entity that controls,
|
||||||
|
is controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, "control" means (a) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or otherwise,
|
||||||
|
or (b) ownership of more than fifty percent (50%) of the outstanding shares
|
||||||
|
or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive
|
||||||
|
license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark) Licensable
|
||||||
|
by such Contributor to use, reproduce, make available, modify, display, perform,
|
||||||
|
distribute, and otherwise exploit its Contributions, either on an unmodified
|
||||||
|
basis, with Modifications, or as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions or
|
||||||
|
its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's modifications
|
||||||
|
of Covered Software, or (ii) the combination of its Contributions with other
|
||||||
|
software (except as part of its Contributor Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the notice
|
||||||
|
requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to distribute
|
||||||
|
the Covered Software under a subsequent version of this License (see Section
|
||||||
|
10.2) or under the terms of a Secondary License (if permitted under the terms
|
||||||
|
of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the rights
|
||||||
|
to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any Modifications
|
||||||
|
that You create or to which You contribute, must be under the terms of this
|
||||||
|
License. You must inform recipients that the Source Code Form of the Covered
|
||||||
|
Software is governed by the terms of this License, and how they can obtain
|
||||||
|
a copy of this License. You may not attempt to alter or restrict the recipients'
|
||||||
|
rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the Executable
|
||||||
|
Form how they can obtain a copy of such Source Code Form by reasonable means
|
||||||
|
in a timely manner, at a charge no more than the cost of distribution to the
|
||||||
|
recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for the
|
||||||
|
Executable Form does not attempt to limit or alter the recipients' rights
|
||||||
|
in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice, provided
|
||||||
|
that You also comply with the requirements of this License for the Covered
|
||||||
|
Software. If the Larger Work is a combination of Covered Software with a work
|
||||||
|
governed by one or more Secondary Licenses, and the Covered Software is not
|
||||||
|
Incompatible With Secondary Licenses, this License permits You to additionally
|
||||||
|
distribute such Covered Software under the terms of such Secondary License(s),
|
||||||
|
so that the recipient of the Larger Work may, at their option, further distribute
|
||||||
|
the Covered Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered Software,
|
||||||
|
except that You may alter any license notices to the extent required to remedy
|
||||||
|
known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support, indemnity
|
||||||
|
or liability obligations to one or more recipients of Covered Software. However,
|
||||||
|
You may do so only on Your own behalf, and not on behalf of any Contributor.
|
||||||
|
You must make it absolutely clear that any such warranty, support, indemnity,
|
||||||
|
or liability obligation is offered by You alone, and You hereby agree to indemnify
|
||||||
|
every Contributor for any liability incurred by such Contributor as a result
|
||||||
|
of warranty, support, indemnity or liability terms You offer. You may include
|
||||||
|
additional disclaimers of warranty and limitations of liability specific to
|
||||||
|
any jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with
|
||||||
|
all distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be sufficiently
|
||||||
|
detailed for a recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if
|
||||||
|
You fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor are
|
||||||
|
reinstated (a) provisionally, unless and until such Contributor explicitly
|
||||||
|
and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor
|
||||||
|
fails to notify You of the non-compliance by some reasonable means prior to
|
||||||
|
60 days after You have come back into compliance. Moreover, Your grants from
|
||||||
|
a particular Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the first
|
||||||
|
time You have received notice of non-compliance with this License from such
|
||||||
|
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||||
|
of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent infringement
|
||||||
|
claim (excluding declaratory judgment actions, counter-claims, and cross-claims)
|
||||||
|
alleging that a Contributor Version directly or indirectly infringes any patent,
|
||||||
|
then the rights granted to You by any and all Contributors for the Covered
|
||||||
|
Software under Section 2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end
|
||||||
|
user license agreements (excluding distributors and resellers) which have
|
||||||
|
been validly granted by You or Your distributors under this License prior
|
||||||
|
to termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an "as is" basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire risk
|
||||||
|
as to the quality and performance of the Covered Software is with You. Should
|
||||||
|
any Covered Software prove defective in any respect, You (not any Contributor)
|
||||||
|
assume the cost of any necessary servicing, repair, or correction. This disclaimer
|
||||||
|
of warranty constitutes an essential part of this License. No use of any Covered
|
||||||
|
Software is authorized under this License except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any character
|
||||||
|
including, without limitation, damages for lost profits, loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all other commercial
|
||||||
|
damages or losses, even if such party shall have been informed of the possibility
|
||||||
|
of such damages. This limitation of liability shall not apply to liability
|
||||||
|
for death or personal injury resulting from such party's negligence to the
|
||||||
|
extent applicable law prohibits such limitation. Some jurisdictions do not
|
||||||
|
allow the exclusion or limitation of incidental or consequential damages,
|
||||||
|
so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts
|
||||||
|
of a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party's ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it enforceable.
|
||||||
|
Any law or regulation which provides that the language of a contract shall
|
||||||
|
be construed against the drafter shall not be used to construe this License
|
||||||
|
against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section 10.3,
|
||||||
|
no one other than the license steward has the right to modify or publish new
|
||||||
|
versions of this License. Each version will be given a distinguishing version
|
||||||
|
number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or under
|
||||||
|
the terms of any subsequent version published by the license steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to create
|
||||||
|
a new license for such software, you may create and use a modified version
|
||||||
|
of this License if you rename the license and remove any references to the
|
||||||
|
name of the license steward (except to note that such modified license differs
|
||||||
|
from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses under the terms of this version of the License, the notice described
|
||||||
|
in Exhibit B of this License must be attached. Exhibit A - Source Code Form
|
||||||
|
License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
||||||
|
one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file,
|
||||||
|
then You may include the notice in a location (such as a LICENSE file in a
|
||||||
|
relevant directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as defined
|
||||||
|
by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,256 @@
|
||||||
|
# [Go GitVer](https://git.rootprojects.org/root/go-gitver)
|
||||||
|
|
||||||
|
Use **git tags** to add (GoRelesear-compatible) [**semver**](https://semver.org/)
|
||||||
|
to your go package in under 150
|
||||||
|
[lines of code](https://git.rootprojects.org/root/go-gitver/src/branch/master/gitver/gitver.go).
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Goals:
|
||||||
|
|
||||||
|
1. Use an exact `git tag` version, like v1.0.0, when clean
|
||||||
|
2. Translate the `git describe` version (v1.0.0-4-g0000000)
|
||||||
|
to semver (1.0.1-pre4+g0000000) in between releases
|
||||||
|
3. Note when `dirty` (and have build timestamp)
|
||||||
|
|
||||||
|
Fail gracefully when git repo isn't available.
|
||||||
|
```
|
||||||
|
|
||||||
|
# GoDoc
|
||||||
|
|
||||||
|
See <https://pkg.go.dev/git.rootprojects.org/root/go-gitver>.
|
||||||
|
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
1. You define the fallback version and version printing in `main.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:generate go run git.rootprojects.org/root/go-gitver
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
var (
|
||||||
|
commit = "0000000"
|
||||||
|
version = "0.0.0-pre0+0000000"
|
||||||
|
date = "0000-00-00T00:00:00+0000"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if (len(os.Args) > 1 && "version" === os.Args[1]) {
|
||||||
|
fmt.Printf("Foobar v%s (%s) %s\n", version, commit[:7], date)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. You `go generate` or `go run git.rootprojects.org/root/go-gitver` to generate `xversion.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commit = "0921ed1e"
|
||||||
|
version = "1.1.2"
|
||||||
|
date = "2019-07-01T02:32:58-06:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Demo
|
||||||
|
|
||||||
|
Generate an `xversion.go` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run git.rootprojects.org/root/go-gitver
|
||||||
|
cat xversion.go
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
|
package main
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commit = "6dace8255b52e123297a44629bc32c015add310a"
|
||||||
|
version = "1.1.4-pre2+g6dace82"
|
||||||
|
date = "2020-07-16T20:48:15-06:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<small>**Note**: The file is named `xversion.go` by default so that the
|
||||||
|
generated file's `init()` will come later, and thus take priority, over
|
||||||
|
most other files.</small>
|
||||||
|
|
||||||
|
See `go-gitver`s self-generated version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run git.rootprojects.org/root/go-gitver version
|
||||||
|
```
|
||||||
|
|
||||||
|
```txt
|
||||||
|
6dace8255b52e123297a44629bc32c015add310a
|
||||||
|
v1.1.4-pre2+g6dace82
|
||||||
|
2020-07-16T20:48:15-06:00
|
||||||
|
```
|
||||||
|
|
||||||
|
# QuickStart
|
||||||
|
|
||||||
|
Add this to the top of your main file, so that it runs with `go generate`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a file that imports go-gitver (for versioning)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// +build tools
|
||||||
|
|
||||||
|
package example
|
||||||
|
|
||||||
|
import _ "git.rootprojects.org/root/go-gitver"
|
||||||
|
```
|
||||||
|
|
||||||
|
Change you build instructions to be something like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod vendor
|
||||||
|
go generate -mod=vendor ./...
|
||||||
|
go build -mod=vendor -o example cmd/example/*.go
|
||||||
|
```
|
||||||
|
|
||||||
|
You don't have to use `-mod=vendor`, but I highly recommend it (just `go mod tidy; go mod vendor` to start).
|
||||||
|
|
||||||
|
# Options
|
||||||
|
|
||||||
|
```txt
|
||||||
|
version print version and exit
|
||||||
|
--fail exit with non-zero status code on failure
|
||||||
|
--package <name> will set the package name
|
||||||
|
--outfile <name> will replace `xversion.go` with the given file path
|
||||||
|
```
|
||||||
|
|
||||||
|
ENVs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alias for --fail
|
||||||
|
GITVER_FAIL=true
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run -mod=vendor git.rootprojects.org/root/go-gitver version
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
See `examples/basic`
|
||||||
|
|
||||||
|
1. Create a `tools` package in your project
|
||||||
|
2. Guard it against regular builds with `// +build tools`
|
||||||
|
3. Include `_ "git.rootprojects.org/root/go-gitver"` in the imports
|
||||||
|
4. Declare `var commit, version, date string` in your `package main`
|
||||||
|
5. Include `//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver` as well
|
||||||
|
|
||||||
|
`tools/tools.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// +build tools
|
||||||
|
|
||||||
|
// This is a dummy package for build tooling
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "git.rootprojects.org/root/go-gitver"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`main.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:generate go run git.rootprojects.org/root/go-gitver --fail
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
commit = "0000000"
|
||||||
|
version = "0.0.0-pre0+0000000"
|
||||||
|
date = "0000-00-00T00:00:00+0000"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(commit)
|
||||||
|
fmt.Println(version)
|
||||||
|
fmt.Println(date)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using `go mod vendor` (which I highly recommend that you do),
|
||||||
|
you'd modify the `go:generate` ever so slightly:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail
|
||||||
|
```
|
||||||
|
|
||||||
|
The only reason I didn't do that in the example is that I'd be included
|
||||||
|
the repository in itself and that would be... weird.
|
||||||
|
|
||||||
|
# Why a tools package?
|
||||||
|
|
||||||
|
> import "git.rootprojects.org/root/go-gitver" is a program, not an importable package
|
||||||
|
|
||||||
|
Having a tools package with a build tag that you don't use is a nice way to add exact
|
||||||
|
versions of a command package used for tooling to your `go.mod` with `go mod tidy`,
|
||||||
|
without getting the error above.
|
||||||
|
|
||||||
|
# git: behind the curtain
|
||||||
|
|
||||||
|
These are the commands that are used under the hood to produce the versions.
|
||||||
|
|
||||||
|
Shows the git tag + description. Assumes that you're using the semver format `v1.0.0` for your base tags.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git describe --tags --dirty --always
|
||||||
|
# v1.0.0
|
||||||
|
# v1.0.0-1-g0000000
|
||||||
|
# v1.0.0-dirty
|
||||||
|
```
|
||||||
|
|
||||||
|
Show the commit date (when the commit made it into the current tree).
|
||||||
|
Internally we use the current date when the working tree is dirty.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git show v1.0.0-1-g0000000 --format=%cd --date=format:%Y-%m-%dT%H:%M:%SZ%z --no-patch
|
||||||
|
# 2010-01-01T20:30:00Z-0600
|
||||||
|
# fatal: ambiguous argument 'v1.0.0-1-g0000000-dirty': unknown revision or path not in the working tree.
|
||||||
|
```
|
||||||
|
|
||||||
|
Shows the most recent commit.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rev-parse HEAD
|
||||||
|
# 0000000000000000000000000000000000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
### cannot find package "."
|
||||||
|
|
||||||
|
```txt
|
||||||
|
package git.rootprojects.org/root/go-gitver: cannot find package "." in:
|
||||||
|
/Users/me/go-example/vendor/git.rootprojects.org/root/go-gitver
|
||||||
|
cmd/example/example.go:1: running "go": exit status 1
|
||||||
|
```
|
||||||
|
|
||||||
|
You forgot to update deps and re-vendor:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
```
|
|
@ -0,0 +1,104 @@
|
||||||
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/go-gitver/gitver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commit, version, date string
|
||||||
|
var exitCode int
|
||||||
|
var verFile = "xversion.go"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pkg := "main"
|
||||||
|
|
||||||
|
args := os.Args[1:]
|
||||||
|
for i := range args {
|
||||||
|
arg := args[i]
|
||||||
|
if "-f" == arg || "--fail" == arg {
|
||||||
|
exitCode = 1
|
||||||
|
} else if ("--outfile" == arg || "-o" == arg) && len(args) > i+1 {
|
||||||
|
verFile = args[i+1]
|
||||||
|
args[i+1] = ""
|
||||||
|
} else if "--package" == arg && len(args) > i+1 {
|
||||||
|
pkg = args[i+1]
|
||||||
|
args[i+1] = ""
|
||||||
|
} else if "-V" == arg || "version" == arg || "-version" == arg || "--version" == arg {
|
||||||
|
fmt.Println(commit)
|
||||||
|
fmt.Println(version)
|
||||||
|
fmt.Println(date)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if "" != os.Getenv("GITVER_FAIL") && "false" != os.Getenv("GITVER_FAIL") {
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := gitver.ExecAndParse()
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to get git version: %s\n", err)
|
||||||
|
if exitCode > 0 {
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
v = &gitver.Versions{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or overwrite the go file from template
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := versionTpl.Execute(&buf, struct {
|
||||||
|
Package string
|
||||||
|
Timestamp string
|
||||||
|
Version string
|
||||||
|
Commit string
|
||||||
|
}{
|
||||||
|
Package: pkg,
|
||||||
|
Timestamp: v.Timestamp.Format(time.RFC3339),
|
||||||
|
Version: v.Version,
|
||||||
|
Commit: v.Rev,
|
||||||
|
}); nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format
|
||||||
|
src, err := format.Source(buf.Bytes())
|
||||||
|
if nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to disk (in the Current Working Directory)
|
||||||
|
f, err := os.Create(verFile)
|
||||||
|
if nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if _, err := f.Write(src); nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionTpl = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
|
||||||
|
package {{ .Package }}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
{{ if .Commit -}}
|
||||||
|
commit = "{{ .Commit }}"
|
||||||
|
{{ end -}}
|
||||||
|
{{ if .Version -}}
|
||||||
|
version = "{{ .Version }}"
|
||||||
|
{{ end -}}
|
||||||
|
date = "{{ .Timestamp }}"
|
||||||
|
}
|
||||||
|
`))
|
|
@ -0,0 +1,3 @@
|
||||||
|
module git.rootprojects.org/root/go-gitver/v2
|
||||||
|
|
||||||
|
go 1.12
|
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// use recently generated version info as a fallback
|
||||||
|
// for when git isn't present (i.e. go run <url>)
|
||||||
|
func init() {
|
||||||
|
commit = "37c1fd4b5694fd62c9f0d6ad1df47d938accbeec"
|
||||||
|
version = "2.0.0-pre1-dirty"
|
||||||
|
date = "2020-10-10T16:05:59-06:00"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
/keypairs
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.*.sw*
|
|
@ -0,0 +1 @@
|
||||||
|
AJ ONeal <aj@therootcompany.com> (https://therootcompany.com)
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018-2019 Big Squid, Inc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,63 @@
|
||||||
|
# [keypairs](https://git.rootprojects.org/root/keypairs)
|
||||||
|
|
||||||
|
JSON Web Key (JWK) support and type safety lightly placed over top of Go's `crypto/ecdsa` and `crypto/rsa`
|
||||||
|
|
||||||
|
Useful for JWT, JOSE, etc.
|
||||||
|
|
||||||
|
```go
|
||||||
|
key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER)
|
||||||
|
|
||||||
|
pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER)
|
||||||
|
|
||||||
|
jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day))
|
||||||
|
|
||||||
|
kid, err := keypairs.ThumbprintPublicKey(pub)
|
||||||
|
```
|
||||||
|
|
||||||
|
# GoDoc API Documentation
|
||||||
|
|
||||||
|
See <https://pkg.go.dev/git.rootprojects.org/root/keypairs>
|
||||||
|
|
||||||
|
# Philosophy
|
||||||
|
|
||||||
|
Go's standard library is great.
|
||||||
|
|
||||||
|
Go has _excellent_ crytography support and provides wonderful
|
||||||
|
primitives for dealing with them.
|
||||||
|
|
||||||
|
I prefer to stay as close to Go's `crypto` package as possible,
|
||||||
|
just adding a light touch for JWT support and type safety.
|
||||||
|
|
||||||
|
# Type Safety
|
||||||
|
|
||||||
|
`crypto.PublicKey` is a "marker interface", meaning that it is **not typesafe**!
|
||||||
|
|
||||||
|
`go-keypairs` defines `type keypairs.PrivateKey interface { Public() crypto.PublicKey }`,
|
||||||
|
which is implemented by `crypto/rsa` and `crypto/ecdsa`
|
||||||
|
(but not `crypto/dsa`, which we really don't care that much about).
|
||||||
|
|
||||||
|
Go1.15 will add `[PublicKey.Equal(crypto.PublicKey)](https://github.com/golang/go/issues/21704)`,
|
||||||
|
which will make it possible to remove the additional wrapper over `PublicKey`
|
||||||
|
and use an interface instead.
|
||||||
|
|
||||||
|
Since there are no common methods between `rsa.PublicKey` and `ecdsa.PublicKey`,
|
||||||
|
go-keypairs lightly wraps each to implement `Thumbprint() string` (part of the JOSE/JWK spec).
|
||||||
|
|
||||||
|
## JSON Web Key (JWK) as a "codec"
|
||||||
|
|
||||||
|
Although there are many, many ways that JWKs could be interpreted
|
||||||
|
(possibly why they haven't made it into the standard library), `go-keypairs`
|
||||||
|
follows the basic pattern of `encoding/x509` to `Parse` and `Marshal`
|
||||||
|
only the most basic and most meaningful parts of a key.
|
||||||
|
|
||||||
|
I highly recommend that you use `Thumbprint()` for `KeyID` you also
|
||||||
|
get the benefit of not losing information when encoding and decoding
|
||||||
|
between the ASN.1, x509, PEM, and JWK formats.
|
||||||
|
|
||||||
|
# LICENSE
|
||||||
|
|
||||||
|
Copyright (c) 2020-present AJ ONeal \
|
||||||
|
Copyright (c) 2018-2019 Big Squid, Inc.
|
||||||
|
|
||||||
|
This work is licensed under the terms of the MIT license. \
|
||||||
|
For a copy, see <https://opensource.org/licenses/MIT>.
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -u
|
||||||
|
|
||||||
|
go build -mod=vendor cmd/keypairs/*.go
|
||||||
|
./keypairs gen > testkey.jwk.json 2> testpub.jwk.json
|
||||||
|
|
||||||
|
./keypairs sign --exp 1h ./testkey.jwk.json '{"foo":"bar"}' > testjwt.txt 2> testjws.json
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Should pass:"
|
||||||
|
./keypairs verify ./testpub.jwk.json testjwt.txt > /dev/null
|
||||||
|
./keypairs verify ./testpub.jwk.json "$(cat testjwt.txt)" > /dev/null
|
||||||
|
./keypairs verify ./testpub.jwk.json testjws.json > /dev/null
|
||||||
|
./keypairs verify ./testpub.jwk.json "$(cat testjws.json)" > /dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Should fail:"
|
||||||
|
./keypairs sign --exp -1m ./testkey.jwk.json '{"bar":"foo"}' > errjwt.txt 2> errjws.json
|
||||||
|
./keypairs verify ./testpub.jwk.json errjwt.txt > /dev/null
|
365
vendor/git.rootprojects.org/root/keypairs/cmd/keypairs/keypairs.go
generated
vendored
Normal file
365
vendor/git.rootprojects.org/root/keypairs/cmd/keypairs/keypairs.go
generated
vendored
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
name = "keypairs"
|
||||||
|
version = "0.0.0"
|
||||||
|
date = "0001-01-01T00:00:00Z"
|
||||||
|
commit = "0000000"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
ver()
|
||||||
|
fmt.Println("Usage")
|
||||||
|
fmt.Printf(" %s <command> [flags] args...\n", name)
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Printf("See usage: %s help <command>\n", name)
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("Commands:")
|
||||||
|
fmt.Println(" version")
|
||||||
|
fmt.Println(" gen")
|
||||||
|
fmt.Println(" sign")
|
||||||
|
fmt.Println(" verify")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("Examples:")
|
||||||
|
fmt.Println(" keypairs gen -o key.jwk.json [--pub <public-key>]")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println(" keypairs sign --exp 15m key.jwk.json payload.json")
|
||||||
|
fmt.Println(" keypairs sign --exp 15m key.jwk.json '{ \"sub\": \"xxxx\" }'")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println(" keypairs verify ./pub.jwk.json 'xxxx.yyyy.zzzz'")
|
||||||
|
// TODO fmt.Println(" keypairs verify --issuer https://example.com '{ \"sub\": \"xxxx\" }'")
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ver() {
|
||||||
|
fmt.Printf("%s v%s %s (%s)\n", name, version, commit[:7], date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := os.Args[:]
|
||||||
|
|
||||||
|
if "help" == args[1] {
|
||||||
|
// top-level help
|
||||||
|
if 2 == len(args) {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// move help to subcommand argument
|
||||||
|
self := args[0]
|
||||||
|
args = append([]string{self}, args[2:]...)
|
||||||
|
args = append(args, "--help")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[1] {
|
||||||
|
case "version":
|
||||||
|
ver()
|
||||||
|
os.Exit(0)
|
||||||
|
return
|
||||||
|
case "gen":
|
||||||
|
gen(args[2:])
|
||||||
|
case "sign":
|
||||||
|
sign(args[2:])
|
||||||
|
case "verify":
|
||||||
|
verify(args[2:])
|
||||||
|
default:
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gen(args []string) {
|
||||||
|
var keyname string
|
||||||
|
var pubname string
|
||||||
|
flags := flag.NewFlagSet("gen", flag.ExitOnError)
|
||||||
|
flags.StringVar(&keyname, "o", "", "private key file (ex: key.jwk.json or key.pem)")
|
||||||
|
flags.StringVar(&pubname, "pub", "", "public key file (ex: pub.jwk.json or pub.pem)")
|
||||||
|
flags.Parse(args)
|
||||||
|
|
||||||
|
key := keypairs.NewDefaultPrivateKey()
|
||||||
|
marshalPriv(key, keyname)
|
||||||
|
pub := keypairs.NewPublicKey(key.Public())
|
||||||
|
marshalPub(pub, pubname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sign(args []string) {
|
||||||
|
var exp time.Duration
|
||||||
|
flags := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||||
|
flags.DurationVar(&exp, "exp", 0, "duration until token expires (Default 15m)")
|
||||||
|
flags.Parse(args)
|
||||||
|
if len(flags.Args()) <= 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: keypairs sign --exp 1h <private PEM or JWK> ./payload.json\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyname := flags.Args()[0]
|
||||||
|
payload := flags.Args()[1]
|
||||||
|
|
||||||
|
key, err := readKey(keyname)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" == payload {
|
||||||
|
// TODO should this be null? I forget
|
||||||
|
payload = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(payload)
|
||||||
|
claims := map[string]interface{}{}
|
||||||
|
if nil != err {
|
||||||
|
var err2 error
|
||||||
|
err2 = json.Unmarshal([]byte(payload), &claims)
|
||||||
|
if nil != err2 {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"could not read payload as file (or parse as string) %q: %s\n", payload, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if 0 == len(claims) {
|
||||||
|
var err3 error
|
||||||
|
err3 = json.Unmarshal(b, &claims)
|
||||||
|
if nil != err3 {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"could not parse palyoad from file %q: %s\n", payload, err3)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != exp {
|
||||||
|
claims["exp"] = exp.Seconds()
|
||||||
|
}
|
||||||
|
if _, ok := claims["exp"]; !ok {
|
||||||
|
claims["exp"] = (15 * time.Minute).Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
jws, err := keypairs.SignClaims(key, nil, claims)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "could not sign claims: %v\n%#v\n", err, claims)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ = json.Marshal(&jws)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", indentJSON(b))
|
||||||
|
fmt.Fprintf(os.Stdout, "%s\n", keypairs.JWSToJWT(jws))
|
||||||
|
}
|
||||||
|
|
||||||
|
func verify(args []string) {
|
||||||
|
flags := flag.NewFlagSet("verify", flag.ExitOnError)
|
||||||
|
flags.Usage = func() {
|
||||||
|
fmt.Println("Usage: keypairs verify <public key> <jwt-or-jwt>")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println(" <public key>: a File or String of an EC or RSA key in JWK or PEM format")
|
||||||
|
fmt.Println(" <jwt-or-jws>: a JWT or JWS File or String, if JWS the payload must be Base64")
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
flags.Parse(args)
|
||||||
|
if len(flags.Args()) <= 1 {
|
||||||
|
flags.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubname := flags.Args()[0]
|
||||||
|
payload := flags.Args()[1]
|
||||||
|
|
||||||
|
pub, err := readPub(pubname)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jws, err := readJWS(payload)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := json.Marshal(&jws)
|
||||||
|
fmt.Fprintf(os.Stdout, "%s\n", indentJSON(b))
|
||||||
|
|
||||||
|
errs := keypairs.VerifyClaims(pub, jws)
|
||||||
|
if nil != errs {
|
||||||
|
fmt.Fprintf(os.Stderr, "error:\n")
|
||||||
|
for _, err := range errs {
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%v\n", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Signature is Valid\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readKey(keyname string) (keypairs.PrivateKey, error) {
|
||||||
|
var key keypairs.PrivateKey = nil
|
||||||
|
|
||||||
|
// Read as file
|
||||||
|
b, err := ioutil.ReadFile(keyname)
|
||||||
|
if nil != err {
|
||||||
|
// Tis not a file! Perhaps a string?
|
||||||
|
var err2 error
|
||||||
|
key, err2 = keypairs.ParsePrivateKey([]byte(keyname))
|
||||||
|
if nil != err2 {
|
||||||
|
// Neither a valid string. Blast!
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not read private key as file (or parse as string) %q:\n%s",
|
||||||
|
keyname, err2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil == key {
|
||||||
|
var err3 error
|
||||||
|
key, err3 = keypairs.ParsePrivateKey(b)
|
||||||
|
if nil != err3 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not parse private key from file %q:\n%s",
|
||||||
|
keyname, err3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPub(pubname string) (keypairs.PublicKey, error) {
|
||||||
|
var pub keypairs.PublicKey = nil
|
||||||
|
|
||||||
|
// Read as file
|
||||||
|
b, err := ioutil.ReadFile(pubname)
|
||||||
|
if nil != err {
|
||||||
|
// No file? Try as string!
|
||||||
|
var err2 error
|
||||||
|
pub, err2 = keypairs.ParsePublicKey([]byte(pubname))
|
||||||
|
if nil != err2 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not read public key as file (or parse as string) %q:\n%w",
|
||||||
|
pubname, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oh, it was a file.
|
||||||
|
if nil == pub {
|
||||||
|
var err3 error
|
||||||
|
pub, err3 = keypairs.ParsePublicKey(b)
|
||||||
|
if nil != err3 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not parse public key from file %q:\n%w",
|
||||||
|
pubname, err3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readJWS(payload string) (*keypairs.JWS, error) {
|
||||||
|
// Is it a file?
|
||||||
|
b, err := ioutil.ReadFile(payload)
|
||||||
|
if nil != err {
|
||||||
|
// Or a JWS or JWS String!?
|
||||||
|
b = []byte(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either way, we have some bytes now
|
||||||
|
jws := &keypairs.JWS{}
|
||||||
|
jwt := string(b)
|
||||||
|
jwsb := []byte(jwt)
|
||||||
|
if !strings.Contains(jwt, " \t\n{}[]") {
|
||||||
|
jws = keypairs.JWTToJWS(string(b))
|
||||||
|
if nil != jws {
|
||||||
|
b, _ = json.Marshal(jws)
|
||||||
|
jwsb = (b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And now we have a string that may be a JWS
|
||||||
|
if err := json.Unmarshal(jwsb, &jws); nil != err {
|
||||||
|
// Nope, it's not
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not read signed payload from file or string as JWT or JWS %q:\n%w",
|
||||||
|
payload, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := jws.DecodeComponents(); nil != err {
|
||||||
|
// bah! so close!
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not decode the JWS Header and Claims components: %w\n%s",
|
||||||
|
err, string(jwsb),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPriv(key keypairs.PrivateKey, keyname string) {
|
||||||
|
if "" == keyname {
|
||||||
|
b := indentJSON(keypairs.MarshalJWKPrivateKey(key))
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, string(b)+"\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
if strings.HasSuffix(keyname, ".json") {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPrivateKey(key))
|
||||||
|
} else if strings.HasSuffix(keyname, ".pem") {
|
||||||
|
b, _ = keypairs.MarshalPEMPrivateKey(key)
|
||||||
|
} else if strings.HasSuffix(keyname, ".der") {
|
||||||
|
b, _ = keypairs.MarshalDERPrivateKey(key)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "private key extension should be .jwk.json, .pem, or .der")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(keyname, b, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPub(pub keypairs.PublicKey, pubname string) {
|
||||||
|
var b []byte
|
||||||
|
if "" == pubname {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, string(b)+"\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(pubname, ".json") {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
} else if strings.HasSuffix(pubname, ".pem") {
|
||||||
|
b, _ = keypairs.MarshalPEMPublicKey(pub)
|
||||||
|
} else if strings.HasSuffix(pubname, ".der") {
|
||||||
|
b, _ = keypairs.MarshalDERPublicKey(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(pubname, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func indentJSON(b []byte) []byte {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
_ = json.Unmarshal(b, &m)
|
||||||
|
b, _ = json.MarshalIndent(&m, "", " ")
|
||||||
|
return append(b, '\n')
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Package keypairs complements Go's standard keypair-related packages
|
||||||
|
(encoding/pem, crypto/x509, crypto/rsa, crypto/ecdsa, crypto/elliptic)
|
||||||
|
with JWK encoding support and typesafe PrivateKey and PublicKey interfaces.
|
||||||
|
|
||||||
|
Basics
|
||||||
|
|
||||||
|
key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER)
|
||||||
|
|
||||||
|
pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER)
|
||||||
|
|
||||||
|
jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day))
|
||||||
|
|
||||||
|
kid, err := keypairs.ThumbprintPublicKey(pub)
|
||||||
|
|
||||||
|
Convenience functions are available which will fetch keys
|
||||||
|
(or retrieve them from cache) via OIDC, .well-known/jwks.json, and direct urls.
|
||||||
|
All keys are cached by Thumbprint, as well as kid(@issuer), if available.
|
||||||
|
|
||||||
|
import "git.rootprojects.org/root/keypairs/keyfetch"
|
||||||
|
|
||||||
|
pubs, err := keyfetch.OIDCJWKs("https://example.com/")
|
||||||
|
pubs, err := keyfetch.OIDCJWK(ThumbOrKeyID, "https://example.com/")
|
||||||
|
|
||||||
|
pubs, err := keyfetch.WellKnownJWKs("https://example.com/")
|
||||||
|
pubs, err := keyfetch.WellKnownJWK(ThumbOrKeyID, "https://example.com/")
|
||||||
|
|
||||||
|
pubs, err := keyfetch.JWKs("https://example.com/path/to/jwks/")
|
||||||
|
pubs, err := keyfetch.JWK(ThumbOrKeyID, "https://example.com/path/to/jwks/")
|
||||||
|
|
||||||
|
// From URL
|
||||||
|
pub, err := keyfetch.Fetch("https://example.com/jwk.json")
|
||||||
|
|
||||||
|
// From Cache only
|
||||||
|
pub := keyfetch.Get(thumbprint, "https://example.com/jwk.json")
|
||||||
|
|
||||||
|
A non-caching version with the same capabilities is also available.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package keypairs
|
|
@ -0,0 +1,69 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"io"
|
||||||
|
mathrand "math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randReader io.Reader = rand.Reader
|
||||||
|
var allowMocking = false
|
||||||
|
|
||||||
|
// KeyOptions are the things that we may need to know about a request to fulfill it properly
|
||||||
|
type keyOptions struct {
|
||||||
|
//Key string `json:"key"`
|
||||||
|
KeyType string `json:"kty"`
|
||||||
|
mockSeed int64 //`json:"-"`
|
||||||
|
//SeedStr string `json:"seed"`
|
||||||
|
//Claims Object `json:"claims"`
|
||||||
|
//Header Object `json:"header"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *keyOptions) nextReader() io.Reader {
|
||||||
|
if allowMocking {
|
||||||
|
return o.maybeMockReader()
|
||||||
|
}
|
||||||
|
return randReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultPrivateKey generates a key with reasonable strength.
|
||||||
|
// Today that means a 256-bit equivalent - either RSA 2048 or EC P-256.
|
||||||
|
func NewDefaultPrivateKey() PrivateKey {
|
||||||
|
// insecure random is okay here,
|
||||||
|
// it's just used for a coin toss
|
||||||
|
mathrand.Seed(time.Now().UnixNano())
|
||||||
|
coin := mathrand.Int()
|
||||||
|
|
||||||
|
// the idea here is that we want to make
|
||||||
|
// it dead simple to support RSA and EC
|
||||||
|
// so it shouldn't matter which is used
|
||||||
|
if 0 == coin%2 {
|
||||||
|
return newPrivateKey(&keyOptions{
|
||||||
|
KeyType: "RSA",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return newPrivateKey(&keyOptions{
|
||||||
|
KeyType: "EC",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPrivateKey generates a 256-bit entropy RSA or ECDSA private key
|
||||||
|
func newPrivateKey(opts *keyOptions) PrivateKey {
|
||||||
|
var privkey PrivateKey
|
||||||
|
|
||||||
|
if "RSA" == opts.KeyType {
|
||||||
|
keylen := 2048
|
||||||
|
privkey, _ = rsa.GenerateKey(opts.nextReader(), keylen)
|
||||||
|
if allowMocking {
|
||||||
|
privkey = maybeDerandomizeMockKey(privkey, keylen, opts)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: EC keys may also suffer the same random problems in the future
|
||||||
|
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader())
|
||||||
|
}
|
||||||
|
return privkey
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module git.rootprojects.org/root/keypairs
|
||||||
|
|
||||||
|
go 1.12
|
|
@ -0,0 +1,69 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JWK abstracts EC and RSA keys
|
||||||
|
type JWK interface {
|
||||||
|
marshalJWK() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECJWK is the EC variant
|
||||||
|
type ECJWK struct {
|
||||||
|
KeyID string `json:"kid,omitempty"`
|
||||||
|
Curve string `json:"crv"`
|
||||||
|
X string `json:"x"`
|
||||||
|
Y string `json:"y"`
|
||||||
|
Use []string `json:"use,omitempty"`
|
||||||
|
Seed string `json:"_seed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *ECJWK) marshalJWK() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, k.Curve, k.X, k.Y)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAJWK is the RSA variant
|
||||||
|
type RSAJWK struct {
|
||||||
|
KeyID string `json:"kid,omitempty"`
|
||||||
|
Exp string `json:"e"`
|
||||||
|
N string `json:"n"`
|
||||||
|
Use []string `json:"use,omitempty"`
|
||||||
|
Seed string `json:"_seed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *RSAJWK) marshalJWK() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, k.Exp, k.N)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// ToPublicJWK exposes only the public parts
|
||||||
|
func ToPublicJWK(pubkey PublicKey) JWK {
|
||||||
|
switch k := pubkey.Key().(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return ECToPublicJWK(k)
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return RSAToPublicJWK(k)
|
||||||
|
default:
|
||||||
|
panic(errors.New("impossible key type"))
|
||||||
|
//return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECToPublicJWK will output the most minimal version of an EC JWK (no key id, no "use" flag, nada)
|
||||||
|
func ECToPublicJWK(k *ecdsa.PublicKey) *ECJWK {
|
||||||
|
return &ECJWK{
|
||||||
|
Curve: k.Curve.Params().Name,
|
||||||
|
X: base64.RawURLEncoding.EncodeToString(k.X.Bytes()),
|
||||||
|
Y: base64.RawURLEncoding.EncodeToString(k.Y.Bytes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAToPublicJWK will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada)
|
||||||
|
func RSAToPublicJWK(p *rsa.PublicKey) *RSAJWK {
|
||||||
|
return &RSAJWK{
|
||||||
|
Exp: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()),
|
||||||
|
N: base64.RawURLEncoding.EncodeToString(p.N.Bytes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,63 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JWS is a parsed JWT, representation as signable/verifiable and human-readable parts
|
||||||
|
type JWS struct {
|
||||||
|
Header Object `json:"header"` // JSON
|
||||||
|
Claims Object `json:"claims"` // JSON
|
||||||
|
Protected string `json:"protected"` // base64
|
||||||
|
Payload string `json:"payload"` // base64
|
||||||
|
Signature string `json:"signature"` // base64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWSToJWT joins JWS parts into a JWT as {ProtectedHeader}.{SerializedPayload}.{Signature}.
|
||||||
|
func JWSToJWT(jwt *JWS) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s.%s",
|
||||||
|
jwt.Protected,
|
||||||
|
jwt.Payload,
|
||||||
|
jwt.Signature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTToJWS splits the JWT into its JWS segments
|
||||||
|
func JWTToJWS(jwt string) (jws *JWS) {
|
||||||
|
jwt = strings.TrimSpace(jwt)
|
||||||
|
parts := strings.Split(jwt, ".")
|
||||||
|
if 3 != len(parts) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &JWS{
|
||||||
|
Protected: parts[0],
|
||||||
|
Payload: parts[1],
|
||||||
|
Signature: parts[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeComponents decodes JWS Header and Claims
|
||||||
|
func (jws *JWS) DecodeComponents() error {
|
||||||
|
protected, err := base64.RawURLEncoding.DecodeString(jws.Protected)
|
||||||
|
if nil != err {
|
||||||
|
return errors.New("invalid JWS header base64Url encoding")
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(protected), &jws.Header); nil != err {
|
||||||
|
return errors.New("invalid JWS header")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := base64.RawURLEncoding.DecodeString(jws.Payload)
|
||||||
|
if nil != err {
|
||||||
|
return errors.New("invalid JWS payload base64Url encoding")
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(payload), &jws.Claims); nil != err {
|
||||||
|
return errors.New("invalid JWS claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,516 @@
|
||||||
|
// Package keyfetch retrieve and cache PublicKeys
|
||||||
|
// from OIDC (https://example.com/.well-known/openid-configuration)
|
||||||
|
// and Auth0 (https://example.com/.well-known/jwks.json)
|
||||||
|
// JWKs URLs and expires them when `exp` is reached
|
||||||
|
// (or a default expiry if the key does not provide one).
|
||||||
|
// It uses the keypairs package to Unmarshal the JWKs into their
|
||||||
|
// native types (with a very thin shim to provide the type safety
|
||||||
|
// that Go's crypto.PublicKey and crypto.PrivateKey interfaces lack).
|
||||||
|
package keyfetch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/keypairs"
|
||||||
|
"git.rootprojects.org/root/keypairs/keyfetch/uncached"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO should be ErrInvalidJWKURL
|
||||||
|
|
||||||
|
// EInvalidJWKURL means that the url did not provide JWKs
|
||||||
|
var EInvalidJWKURL = errors.New("url does not lead to valid JWKs")
|
||||||
|
|
||||||
|
// KeyCache is an in-memory key cache
|
||||||
|
var KeyCache = map[string]CachableKey{}
|
||||||
|
|
||||||
|
// KeyCacheMux is used to guard the in-memory cache
|
||||||
|
var KeyCacheMux = sync.Mutex{}
|
||||||
|
|
||||||
|
// ErrInsecureDomain means that plain http was used where https was expected
|
||||||
|
var ErrInsecureDomain = errors.New("Whitelists should only allow secure URLs (i.e. https://). To allow unsecured private networking (i.e. Docker) pass PrivateWhitelist as a list of private URLs")
|
||||||
|
|
||||||
|
// TODO Cacheable key (shouldn't this be private)?
|
||||||
|
|
||||||
|
// CachableKey represents
|
||||||
|
type CachableKey struct {
|
||||||
|
Key keypairs.PublicKey
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe TODO use this poor-man's enum to allow kids thumbs to be accepted by the same method?
|
||||||
|
/*
|
||||||
|
type KeyID string
|
||||||
|
|
||||||
|
func (kid KeyID) ID() string {
|
||||||
|
return string(kid)
|
||||||
|
}
|
||||||
|
func (kid KeyID) isID() {}
|
||||||
|
|
||||||
|
type Thumbprint string
|
||||||
|
|
||||||
|
func (thumb Thumbprint) ID() string {
|
||||||
|
return string(thumb)
|
||||||
|
}
|
||||||
|
func (thumb Thumbprint) isID() {}
|
||||||
|
|
||||||
|
type ID interface {
|
||||||
|
ID() string
|
||||||
|
isID()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// StaleTime defines when public keys should be renewed (15 minutes by default)
|
||||||
|
var StaleTime = 15 * time.Minute
|
||||||
|
|
||||||
|
// DefaultKeyDuration defines how long a key should be considered fresh (48 hours by default)
|
||||||
|
var DefaultKeyDuration = 48 * time.Hour
|
||||||
|
|
||||||
|
// MinimumKeyDuration defines the minimum time that a key will be cached (1 hour by default)
|
||||||
|
var MinimumKeyDuration = time.Hour
|
||||||
|
|
||||||
|
// MaximumKeyDuration defines the maximum time that a key will be cached (72 hours by default)
|
||||||
|
var MaximumKeyDuration = 72 * time.Hour
|
||||||
|
|
||||||
|
// PublicKeysMap is a newtype for a map of keypairs.PublicKey
|
||||||
|
type PublicKeysMap map[string]keypairs.PublicKey
|
||||||
|
|
||||||
|
// OIDCJWKs fetches baseURL + ".well-known/openid-configuration" and then fetches and returns the Public Keys.
|
||||||
|
func OIDCJWKs(baseURL string) (PublicKeysMap, error) {
|
||||||
|
maps, keys, err := uncached.OIDCJWKs(baseURL)
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cacheKeys(maps, keys, baseURL)
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OIDCJWK fetches baseURL + ".well-known/openid-configuration" and then returns the key matching kid (or thumbprint)
|
||||||
|
func OIDCJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) {
|
||||||
|
return immediateOneOrFetch(kidOrThumb, iss, uncached.OIDCJWKs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WellKnownJWKs fetches baseURL + ".well-known/jwks.json" and caches and returns the keys
|
||||||
|
func WellKnownJWKs(kidOrThumb, iss string) (PublicKeysMap, error) {
|
||||||
|
maps, keys, err := uncached.WellKnownJWKs(iss)
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cacheKeys(maps, keys, iss)
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WellKnownJWK fetches baseURL + ".well-known/jwks.json" and returns the key matching kid (or thumbprint)
|
||||||
|
func WellKnownJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) {
|
||||||
|
return immediateOneOrFetch(kidOrThumb, iss, uncached.WellKnownJWKs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWKs returns a map of keys identified by their thumbprint
|
||||||
|
// (since kid may or may not be present)
|
||||||
|
func JWKs(jwksurl string) (PublicKeysMap, error) {
|
||||||
|
maps, keys, err := uncached.JWKs(jwksurl)
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iss := strings.Replace(jwksurl, ".well-known/jwks.json", "", 1)
|
||||||
|
cacheKeys(maps, keys, iss)
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWK tries to return a key from cache, falling back to the /.well-known/jwks.json of the issuer
|
||||||
|
func JWK(kidOrThumb, iss string) (keypairs.PublicKey, error) {
|
||||||
|
return immediateOneOrFetch(kidOrThumb, iss, uncached.JWKs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEM tries to return a key from cache, falling back to the specified PEM url
|
||||||
|
func PEM(url string) (keypairs.PublicKey, error) {
|
||||||
|
// url is kid in this case
|
||||||
|
return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
m, key, err := uncached.PEM(url)
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// put in a map, just for caching
|
||||||
|
maps := map[string]map[string]string{}
|
||||||
|
maps[key.Thumbprint()] = m
|
||||||
|
maps[url] = m
|
||||||
|
|
||||||
|
keys := map[string]keypairs.PublicKey{}
|
||||||
|
keys[key.Thumbprint()] = key
|
||||||
|
keys[url] = key
|
||||||
|
|
||||||
|
return maps, keys, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch returns a key from cache, falling back to an exact url as the "issuer"
|
||||||
|
func Fetch(url string) (keypairs.PublicKey, error) {
|
||||||
|
// url is kid in this case
|
||||||
|
return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
m, key, err := uncached.Fetch(url)
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// put in a map, just for caching
|
||||||
|
maps := map[string]map[string]string{}
|
||||||
|
maps[key.Thumbprint()] = m
|
||||||
|
|
||||||
|
keys := map[string]keypairs.PublicKey{}
|
||||||
|
keys[key.Thumbprint()] = key
|
||||||
|
|
||||||
|
return maps, keys, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a key from cache, or returns an error.
|
||||||
|
// The issuer string may be empty if using a thumbprint rather than a kid.
|
||||||
|
func Get(kidOrThumb, iss string) keypairs.PublicKey {
|
||||||
|
if pub := get(kidOrThumb, iss); nil != pub {
|
||||||
|
return pub.Key
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(kidOrThumb, iss string) *CachableKey {
|
||||||
|
iss = normalizeIssuer(iss)
|
||||||
|
KeyCacheMux.Lock()
|
||||||
|
defer KeyCacheMux.Unlock()
|
||||||
|
|
||||||
|
// we're safe to check the cache by kid alone
|
||||||
|
// by virtue that we never set it by kid alone
|
||||||
|
hit, ok := KeyCache[kidOrThumb]
|
||||||
|
if ok {
|
||||||
|
if now := time.Now(); hit.Expiry.Sub(now) > 0 {
|
||||||
|
// only return non-expired keys
|
||||||
|
return &hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id := kidOrThumb + "@" + iss
|
||||||
|
hit, ok = KeyCache[id]
|
||||||
|
if ok {
|
||||||
|
if now := time.Now(); hit.Expiry.Sub(now) > 0 {
|
||||||
|
// only return non-expired keys
|
||||||
|
return &hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func immediateOneOrFetch(kidOrThumb, iss string, fetcher myfetcher) (keypairs.PublicKey, error) {
|
||||||
|
now := time.Now()
|
||||||
|
key := get(kidOrThumb, iss)
|
||||||
|
|
||||||
|
if nil == key {
|
||||||
|
return fetchAndSelect(kidOrThumb, iss, fetcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch just a little before the key actually expires
|
||||||
|
if key.Expiry.Sub(now) <= StaleTime {
|
||||||
|
go fetchAndSelect(kidOrThumb, iss, fetcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key.Key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type myfetcher func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error)
|
||||||
|
|
||||||
|
func fetchAndSelect(id, baseURL string, fetcher myfetcher) (keypairs.PublicKey, error) {
|
||||||
|
maps, keys, err := fetcher(baseURL)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cacheKeys(maps, keys, baseURL)
|
||||||
|
|
||||||
|
for i := range keys {
|
||||||
|
key := keys[i]
|
||||||
|
|
||||||
|
if id == key.Thumbprint() {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == key.KeyID() {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Key identified by '%s' was not found at %s", id, baseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheKeys(maps map[string]map[string]string, keys map[string]keypairs.PublicKey, issuer string) {
|
||||||
|
for i := range keys {
|
||||||
|
key := keys[i]
|
||||||
|
m := maps[i]
|
||||||
|
iss := issuer
|
||||||
|
if "" != m["iss"] {
|
||||||
|
iss = m["iss"]
|
||||||
|
}
|
||||||
|
iss = normalizeIssuer(iss)
|
||||||
|
cacheKey(m["kid"], iss, m["exp"], key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheKey(kid, iss, expstr string, pub keypairs.PublicKey) error {
|
||||||
|
var expiry time.Time
|
||||||
|
iss = normalizeIssuer(iss)
|
||||||
|
|
||||||
|
exp, _ := strconv.ParseInt(expstr, 10, 64)
|
||||||
|
if 0 == exp {
|
||||||
|
// use default
|
||||||
|
expiry = time.Now().Add(DefaultKeyDuration)
|
||||||
|
} else if exp < time.Now().Add(MinimumKeyDuration).Unix() || exp > time.Now().Add(MaximumKeyDuration).Unix() {
|
||||||
|
// use at least one hour
|
||||||
|
expiry = time.Now().Add(MinimumKeyDuration)
|
||||||
|
} else {
|
||||||
|
expiry = time.Unix(exp, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCacheMux.Lock()
|
||||||
|
defer KeyCacheMux.Unlock()
|
||||||
|
// Put the key in the cache by both kid and thumbprint, and set the expiry
|
||||||
|
id := kid + "@" + iss
|
||||||
|
KeyCache[id] = CachableKey{
|
||||||
|
Key: pub,
|
||||||
|
Expiry: expiry,
|
||||||
|
}
|
||||||
|
// Since thumbprints are crypto secure, iss isn't needed
|
||||||
|
thumb := pub.Thumbprint()
|
||||||
|
KeyCache[thumb] = CachableKey{
|
||||||
|
Key: pub,
|
||||||
|
Expiry: expiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear() {
|
||||||
|
KeyCacheMux.Lock()
|
||||||
|
defer KeyCacheMux.Unlock()
|
||||||
|
KeyCache = map[string]CachableKey{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeIssuer(iss string) string {
|
||||||
|
return strings.TrimRight(iss, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTrustedIssuer(iss string, whitelist Whitelist, rs ...*http.Request) bool {
|
||||||
|
if "" == iss {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the http:// and https:// and parse
|
||||||
|
iss = strings.TrimRight(iss, "/") + "/"
|
||||||
|
if strings.HasPrefix(iss, "http://") {
|
||||||
|
// ignore
|
||||||
|
} else if strings.HasPrefix(iss, "//") {
|
||||||
|
return false // TODO
|
||||||
|
} else if !strings.HasPrefix(iss, "https://") {
|
||||||
|
iss = "https://" + iss
|
||||||
|
}
|
||||||
|
issURL, err := url.Parse(iss)
|
||||||
|
if nil != err {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that
|
||||||
|
// * schemes match (https: == https:)
|
||||||
|
// * paths match (/foo/ == /foo/, always with trailing slash added)
|
||||||
|
// * hostnames are compatible (a == b or "sub.foo.com".HasSufix(".foo.com"))
|
||||||
|
for i := range []*url.URL(whitelist) {
|
||||||
|
u := whitelist[i]
|
||||||
|
|
||||||
|
if issURL.Scheme != u.Scheme {
|
||||||
|
continue
|
||||||
|
} else if u.Path != strings.TrimRight(issURL.Path, "/")+"/" {
|
||||||
|
continue
|
||||||
|
} else if issURL.Host != u.Host {
|
||||||
|
if '.' == u.Host[0] && strings.HasSuffix(issURL.Host, u.Host) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// All failures have been handled
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if implicit issuer is available
|
||||||
|
if 0 == len(rs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hasImplicitTrust(issURL, rs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasImplicitTrust relies on the security of DNS and TLS to determine if the
|
||||||
|
// headers of the request can be trusted as identifying the server itself as
|
||||||
|
// a valid issuer, without additional configuration.
|
||||||
|
//
|
||||||
|
// Helpful for testing, but in the wrong hands could easily lead to a zero-day.
|
||||||
|
func hasImplicitTrust(issURL *url.URL, r *http.Request) bool {
|
||||||
|
if nil == r {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check that, if a load balancer exists, it isn't misconfigured
|
||||||
|
proto := r.Header.Get("X-Forwarded-Proto")
|
||||||
|
if "" != proto && proto != "https" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the host
|
||||||
|
// * If TLS, block Domain Fronting
|
||||||
|
// * Otherwise assume trusted proxy
|
||||||
|
// * Otherwise assume test environment
|
||||||
|
var host string
|
||||||
|
if nil != r.TLS {
|
||||||
|
// Note that if this were to be implemented for HTTP/2 it would need to
|
||||||
|
// check all names on the certificate, not just the one with which the
|
||||||
|
// original connection was established. However, not our problem here.
|
||||||
|
// See https://serverfault.com/a/908087/93930
|
||||||
|
if r.TLS.ServerName != r.Host {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
host = r.Host
|
||||||
|
} else {
|
||||||
|
host = r.Header.Get("X-Forwarded-Host")
|
||||||
|
if "" == host {
|
||||||
|
host = r.Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same tests as above, adjusted since it can't handle wildcards and, since
|
||||||
|
// the path is variable, we make the assumption that a child can trust a
|
||||||
|
// parent, but that a parent cannot trust a child.
|
||||||
|
if r.Host != issURL.Host {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(strings.TrimRight(r.URL.Path, "/")+"/", issURL.Path) {
|
||||||
|
// Ex: Request URL Token Issuer
|
||||||
|
// !"https:example.com/johndoe/api/dothing".HasPrefix("https:example.com/")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelist is a newtype for an array of URLs
|
||||||
|
type Whitelist []*url.URL
|
||||||
|
|
||||||
|
// NewWhitelist turns an array of URLs (such as https://example.com/) into
|
||||||
|
// a parsed array of *url.URLs that can be used by the IsTrustedIssuer function
|
||||||
|
func NewWhitelist(issuers []string, privateList ...[]string) (Whitelist, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
list := []*url.URL{}
|
||||||
|
if 0 != len(issuers) {
|
||||||
|
insecure := false
|
||||||
|
list, err = newWhitelist(list, issuers, insecure)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if 0 != len(privateList) && 0 != len(privateList[0]) {
|
||||||
|
insecure := true
|
||||||
|
list, err = newWhitelist(list, privateList[0], insecure)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Whitelist(list), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWhitelist(list []*url.URL, issuers []string, insecure bool) (Whitelist, error) {
|
||||||
|
for i := range issuers {
|
||||||
|
iss := issuers[i]
|
||||||
|
if "" == strings.TrimSpace(iss) {
|
||||||
|
fmt.Println("[Warning] You have an empty string in your keyfetch whitelist.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have a valid http or https prefix
|
||||||
|
// TODO support custom prefixes (i.e. app://) ?
|
||||||
|
if strings.HasPrefix(iss, "http://") {
|
||||||
|
if !insecure {
|
||||||
|
log.Println("Oops! You have an insecure domain in your whitelist: ", iss)
|
||||||
|
return nil, ErrInsecureDomain
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(iss, "//") {
|
||||||
|
// TODO
|
||||||
|
return nil, errors.New("Rather than prefixing with // to support multiple protocols, add them seperately:" + iss)
|
||||||
|
} else if !strings.HasPrefix(iss, "https://") {
|
||||||
|
iss = "https://" + iss
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailing slash as a boundary character, which may or may not denote a directory
|
||||||
|
iss = strings.TrimRight(iss, "/") + "/"
|
||||||
|
u, err := url.Parse(iss)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip any * prefix, for easier comparison later
|
||||||
|
// *.example.com => .example.com
|
||||||
|
if strings.HasPrefix(u.Host, "*.") {
|
||||||
|
u.Host = u.Host[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsTrustedIssuer returns true when the `iss` (i.e. from a token) matches one
|
||||||
|
in the provided whitelist (also matches wildcard domains).
|
||||||
|
|
||||||
|
You may explicitly allow insecure http (i.e. for automated testing) by
|
||||||
|
including http:// Otherwise the scheme in each item of the whitelist should
|
||||||
|
include the "https://" prefix.
|
||||||
|
|
||||||
|
SECURITY CONSIDERATIONS (Please Read)
|
||||||
|
|
||||||
|
You'll notice that *http.Request is optional. It should only be used under these
|
||||||
|
three circumstances:
|
||||||
|
|
||||||
|
1) Something else guarantees http -> https redirection happens before the
|
||||||
|
connection gets here AND this server directly handles TLS/SSL.
|
||||||
|
|
||||||
|
2) If you're using a load balancer or web server, and this doesn't handle
|
||||||
|
TLS/SSL directly, that server is _explicitly_ configured to protect
|
||||||
|
against Domain Fronting attacks. As of 2019, most web servers and load
|
||||||
|
balancers do not protect against that by default.
|
||||||
|
|
||||||
|
3) If you only use it to make your automated integration testing more
|
||||||
|
and it isn't enabled in production.
|
||||||
|
|
||||||
|
Otherwise, DO NOT pass in *http.Request as you will introduce a 0-day
|
||||||
|
vulnerability allowing an attacker to spoof any token issuer of their choice.
|
||||||
|
The only reason I allowed this in a public library where non-experts would
|
||||||
|
encounter it is to make testing easier.
|
||||||
|
*/
|
||||||
|
func (w Whitelist) IsTrustedIssuer(iss string, rs ...*http.Request) bool {
|
||||||
|
return isTrustedIssuer(iss, w, rs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String will generate a space-delimited list of whitelisted URLs
|
||||||
|
func (w Whitelist) String() string {
|
||||||
|
s := []string{}
|
||||||
|
for i := range w {
|
||||||
|
s = append(s, w[i].String())
|
||||||
|
}
|
||||||
|
return strings.Join(s, " ")
|
||||||
|
}
|
183
vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
generated
vendored
Normal file
183
vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Package uncached provides uncached versions of go-keypairs/keyfetch
|
||||||
|
package uncached
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OIDCJWKs gets the OpenID Connect configuration from the baseURL and then calls JWKs with the specified jwks_uri
|
||||||
|
func OIDCJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
baseURL = normalizeBaseURL(baseURL)
|
||||||
|
oidcConf := struct {
|
||||||
|
JWKSURI string `json:"jwks_uri"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// must come in as https://<domain>/
|
||||||
|
url := baseURL + ".well-known/openid-configuration"
|
||||||
|
err := safeFetch(url, func(body io.Reader) error {
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
return decoder.Decode(&oidcConf)
|
||||||
|
})
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return JWKs(oidcConf.JWKSURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WellKnownJWKs calls JWKs with baseURL + /.well-known/jwks.json as constructs the jwks_uri
|
||||||
|
func WellKnownJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
baseURL = normalizeBaseURL(baseURL)
|
||||||
|
url := baseURL + ".well-known/jwks.json"
|
||||||
|
|
||||||
|
return JWKs(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWKs fetches and parses a jwks.json (assuming well-known format)
|
||||||
|
func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
keys := map[string]keypairs.PublicKey{}
|
||||||
|
maps := map[string]map[string]string{}
|
||||||
|
resp := struct {
|
||||||
|
Keys []map[string]interface{} `json:"keys"`
|
||||||
|
}{
|
||||||
|
Keys: make([]map[string]interface{}, 0, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := safeFetch(jwksurl, func(body io.Reader) error {
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
return decoder.Decode(&resp)
|
||||||
|
}); nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range resp.Keys {
|
||||||
|
k := resp.Keys[i]
|
||||||
|
m := getStringMap(k)
|
||||||
|
|
||||||
|
key, err := keypairs.NewJWKPublicKey(m)
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
keys[key.Thumbprint()] = key
|
||||||
|
maps[key.Thumbprint()] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps, keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEM fetches and parses a PEM (assuming well-known format)
|
||||||
|
func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) {
|
||||||
|
var pub keypairs.PublicKey
|
||||||
|
if err := safeFetch(pemurl, func(body io.Reader) error {
|
||||||
|
pem, err := ioutil.ReadAll(body)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pub, err = keypairs.ParsePublicKey(pem)
|
||||||
|
return err
|
||||||
|
}); nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk := map[string]interface{}{}
|
||||||
|
body := bytes.NewBuffer(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
_ = decoder.Decode(&jwk)
|
||||||
|
|
||||||
|
m := getStringMap(jwk)
|
||||||
|
m["kid"] = pemurl
|
||||||
|
|
||||||
|
switch p := pub.(type) {
|
||||||
|
case *keypairs.ECPublicKey:
|
||||||
|
p.KID = pemurl
|
||||||
|
case *keypairs.RSAPublicKey:
|
||||||
|
p.KID = pemurl
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.New("impossible key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch retrieves a single JWK (plain, bare jwk) from a URL (off-spec)
|
||||||
|
func Fetch(url string) (map[string]string, keypairs.PublicKey, error) {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := safeFetch(url, func(body io.Reader) error {
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
return decoder.Decode(&m)
|
||||||
|
}); nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := getStringMap(m)
|
||||||
|
key, err := keypairs.NewJWKPublicKey(n)
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringMap(m map[string]interface{}) map[string]string {
|
||||||
|
n := make(map[string]string)
|
||||||
|
|
||||||
|
// TODO get issuer from x5c, if exists
|
||||||
|
|
||||||
|
// convert map[string]interface{} to map[string]string
|
||||||
|
for j := range m {
|
||||||
|
switch s := m[j].(type) {
|
||||||
|
case string:
|
||||||
|
n[j] = s
|
||||||
|
default:
|
||||||
|
// safely ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
type decodeFunc func(io.Reader) error
|
||||||
|
|
||||||
|
// TODO: also limit the body size
|
||||||
|
func safeFetch(url string, decoder decodeFunc) error {
|
||||||
|
var netTransport = &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
var client = &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
Transport: netTransport,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("User-Agent", "go-keypairs/keyfetch")
|
||||||
|
req.Header.Set("Accept", "application/json;q=0.9,*/*;q=0.8")
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
return decoder(res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBaseURL(iss string) string {
|
||||||
|
return strings.TrimRight(iss, "/") + "/"
|
||||||
|
}
|
|
@ -0,0 +1,645 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidPrivateKey means that the key is not a valid Private Key
|
||||||
|
var ErrInvalidPrivateKey = errors.New("PrivateKey must be of type *rsa.PrivateKey or *ecdsa.PrivateKey")
|
||||||
|
|
||||||
|
// ErrInvalidPublicKey means that the key is not a valid Public Key
|
||||||
|
var ErrInvalidPublicKey = errors.New("PublicKey must be of type *rsa.PublicKey or *ecdsa.PublicKey")
|
||||||
|
|
||||||
|
// ErrParsePublicKey means that the bytes cannot be parsed in any known format
|
||||||
|
var ErrParsePublicKey = errors.New("PublicKey bytes could not be parsed as PEM or DER (PKIX/SPKI, PKCS1, or X509 Certificate) or JWK")
|
||||||
|
|
||||||
|
// ErrParsePrivateKey means that the bytes cannot be parsed in any known format
|
||||||
|
var ErrParsePrivateKey = errors.New("PrivateKey bytes could not be parsed as PEM or DER (PKCS8, SEC1, or PKCS1) or JWK")
|
||||||
|
|
||||||
|
// ErrParseJWK means that the JWK is valid JSON but not a valid JWK
|
||||||
|
var ErrParseJWK = errors.New("JWK is missing required base64-encoded JSON fields")
|
||||||
|
|
||||||
|
// ErrInvalidKeyType means that the key is not an acceptable type
|
||||||
|
var ErrInvalidKeyType = errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'")
|
||||||
|
|
||||||
|
// ErrInvalidCurve means that a non-standard curve was used
|
||||||
|
var ErrInvalidCurve = errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'")
|
||||||
|
|
||||||
|
// ErrUnexpectedPublicKey means that a Private Key was expected
|
||||||
|
var ErrUnexpectedPublicKey = errors.New("PrivateKey was given where PublicKey was expected")
|
||||||
|
|
||||||
|
// ErrUnexpectedPrivateKey means that a Public Key was expected
|
||||||
|
var ErrUnexpectedPrivateKey = errors.New("PublicKey was given where PrivateKey was expected")
|
||||||
|
|
||||||
|
// ErrDevSwapPrivatePublic means that the developer compiled bad code that swapped public and private keys
|
||||||
|
const ErrDevSwapPrivatePublic = "[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected."
|
||||||
|
|
||||||
|
// ErrDevBadKeyType means that the developer compiled bad code that passes the wrong type
|
||||||
|
const ErrDevBadKeyType = "[Developer Error] crypto.PublicKey and crypto.PrivateKey are somewhat deceptive. They're actually empty interfaces that accept any object, even non-crypto objects. You passed an object of type '%T' by mistake."
|
||||||
|
|
||||||
|
// PrivateKey is a zero-cost typesafe substitue for crypto.PrivateKey
|
||||||
|
type PrivateKey interface {
|
||||||
|
Public() crypto.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey thinly veils crypto.PublicKey for type safety
|
||||||
|
type PublicKey interface {
|
||||||
|
crypto.PublicKey
|
||||||
|
Thumbprint() string
|
||||||
|
KeyID() string
|
||||||
|
Key() crypto.PublicKey
|
||||||
|
ExpiresAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECPublicKey adds common methods to *ecdsa.PublicKey for type safety
|
||||||
|
type ECPublicKey struct {
|
||||||
|
PublicKey *ecdsa.PublicKey // empty interface
|
||||||
|
KID string
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAPublicKey adds common methods to *rsa.PublicKey for type safety
|
||||||
|
type RSAPublicKey struct {
|
||||||
|
PublicKey *rsa.PublicKey // empty interface
|
||||||
|
KID string
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||||
|
func (p *ECPublicKey) Thumbprint() string {
|
||||||
|
return ThumbprintUntypedPublicKey(p.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library
|
||||||
|
func (p *ECPublicKey) KeyID() string {
|
||||||
|
return p.KID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the PublicKey
|
||||||
|
func (p *ECPublicKey) Key() crypto.PublicKey {
|
||||||
|
return p.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpireAt sets the time at which this Public Key should be considered invalid
|
||||||
|
func (p *ECPublicKey) ExpireAt(t time.Time) {
|
||||||
|
p.Expiry = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpiresAt gets the time at which this Public Key should be considered invalid
|
||||||
|
func (p *ECPublicKey) ExpiresAt() time.Time {
|
||||||
|
return p.Expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||||
|
func (p *RSAPublicKey) Thumbprint() string {
|
||||||
|
return ThumbprintUntypedPublicKey(p.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library
|
||||||
|
func (p *RSAPublicKey) KeyID() string {
|
||||||
|
return p.KID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the PublicKey
|
||||||
|
func (p *RSAPublicKey) Key() crypto.PublicKey {
|
||||||
|
return p.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpireAt sets the time at which this Public Key should be considered invalid
|
||||||
|
func (p *RSAPublicKey) ExpireAt(t time.Time) {
|
||||||
|
p.Expiry = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpiresAt gets the time at which this Public Key should be considered invalid
|
||||||
|
func (p *RSAPublicKey) ExpiresAt() time.Time {
|
||||||
|
return p.Expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicKey wraps a crypto.PublicKey to make it typesafe.
|
||||||
|
func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey {
|
||||||
|
var k PublicKey
|
||||||
|
switch p := pub.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
eckey := &ECPublicKey{
|
||||||
|
PublicKey: p,
|
||||||
|
}
|
||||||
|
if 0 != len(kid) {
|
||||||
|
eckey.KID = kid[0]
|
||||||
|
} else {
|
||||||
|
eckey.KID = ThumbprintECPublicKey(p)
|
||||||
|
}
|
||||||
|
k = eckey
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
rsakey := &RSAPublicKey{
|
||||||
|
PublicKey: p,
|
||||||
|
}
|
||||||
|
if 0 != len(kid) {
|
||||||
|
rsakey.KID = kid[0]
|
||||||
|
} else {
|
||||||
|
rsakey.KID = ThumbprintRSAPublicKey(p)
|
||||||
|
}
|
||||||
|
k = rsakey
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
panic(errors.New(ErrDevSwapPrivatePublic))
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
panic(errors.New(ErrDevSwapPrivatePublic))
|
||||||
|
case *dsa.PublicKey:
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
case *dsa.PrivateKey:
|
||||||
|
panic(ErrInvalidPrivateKey)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf(ErrDevBadKeyType, pub))
|
||||||
|
}
|
||||||
|
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJWKPublicKey outputs a JWK with its key id (kid) and an optional expiration,
|
||||||
|
// making it suitable for use as an OIDC public key.
|
||||||
|
func MarshalJWKPublicKey(key PublicKey, exp ...time.Time) []byte {
|
||||||
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
||||||
|
switch k := key.Key().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return MarshalRSAPublicKey(k, exp...)
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return MarshalECPublicKey(k, exp...)
|
||||||
|
case *dsa.PublicKey:
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", key, key)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThumbprintPublicKey returns the SHA256 RFC-spec JWK thumbprint
|
||||||
|
func ThumbprintPublicKey(pub PublicKey) string {
|
||||||
|
return ThumbprintUntypedPublicKey(pub.Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThumbprintUntypedPublicKey is a non-typesafe version of ThumbprintPublicKey
|
||||||
|
// (but will still panic, to help you discover bugs in development rather than production).
|
||||||
|
func ThumbprintUntypedPublicKey(pub crypto.PublicKey) string {
|
||||||
|
switch p := pub.(type) {
|
||||||
|
case PublicKey:
|
||||||
|
return ThumbprintUntypedPublicKey(p.Key())
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return ThumbprintECPublicKey(p)
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return ThumbprintRSAPublicKey(p)
|
||||||
|
default:
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalECPublicKey will take an EC key and output a JWK, with optional expiration date
|
||||||
|
func MarshalECPublicKey(k *ecdsa.PublicKey, exp ...time.Time) []byte {
|
||||||
|
thumb := ThumbprintECPublicKey(k)
|
||||||
|
crv := k.Curve.Params().Name
|
||||||
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
||||||
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
||||||
|
expstr := ""
|
||||||
|
if 0 != len(exp) {
|
||||||
|
expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix())
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"crv":%q,"kty":"EC","x":%q,"y":%q}`, thumb, expstr, crv, x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalECPublicKeyWithoutKeyID will output the most minimal version of an EC JWK (no key id, no "use" flag, nada)
|
||||||
|
func MarshalECPublicKeyWithoutKeyID(k *ecdsa.PublicKey) []byte {
|
||||||
|
crv := k.Curve.Params().Name
|
||||||
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
||||||
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, crv, x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThumbprintECPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key
|
||||||
|
func ThumbprintECPublicKey(k *ecdsa.PublicKey) string {
|
||||||
|
thumbprintable := MarshalECPublicKeyWithoutKeyID(k)
|
||||||
|
sha := sha256.Sum256(thumbprintable)
|
||||||
|
return base64.RawURLEncoding.EncodeToString(sha[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalRSAPublicKey will take an RSA key and output a JWK, with optional expiration date
|
||||||
|
func MarshalRSAPublicKey(p *rsa.PublicKey, exp ...time.Time) []byte {
|
||||||
|
thumb := ThumbprintRSAPublicKey(p)
|
||||||
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes())
|
||||||
|
n := base64.RawURLEncoding.EncodeToString(p.N.Bytes())
|
||||||
|
expstr := ""
|
||||||
|
if 0 != len(exp) {
|
||||||
|
expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix())
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"e":%q,"kty":"RSA","n":%q}`, thumb, expstr, e, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalRSAPublicKeyWithoutKeyID will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada)
|
||||||
|
func MarshalRSAPublicKeyWithoutKeyID(p *rsa.PublicKey) []byte {
|
||||||
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes())
|
||||||
|
n := base64.RawURLEncoding.EncodeToString(p.N.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, e, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThumbprintRSAPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key
|
||||||
|
func ThumbprintRSAPublicKey(p *rsa.PublicKey) string {
|
||||||
|
thumbprintable := MarshalRSAPublicKeyWithoutKeyID(p)
|
||||||
|
sha := sha256.Sum256([]byte(thumbprintable))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(sha[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePrivateKey will try to parse the bytes you give it
|
||||||
|
// in any of the supported formats: PEM, DER, PKCS8, PKCS1, SEC1, and JWK
|
||||||
|
func ParsePrivateKey(block []byte) (PrivateKey, error) {
|
||||||
|
blocks, err := getPEMBytes(block)
|
||||||
|
if nil != err {
|
||||||
|
return nil, ErrParsePrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PEM blocks (openssl generates junk metadata blocks for ECs)
|
||||||
|
// or the original DER, or the JWK
|
||||||
|
for i := range blocks {
|
||||||
|
block = blocks[i]
|
||||||
|
if key, err := parsePrivateKey(block); nil == err {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range blocks {
|
||||||
|
block = blocks[i]
|
||||||
|
if _, err := parsePublicKey(block); nil == err {
|
||||||
|
return nil, ErrUnexpectedPublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't parse a key arleady, we failed
|
||||||
|
return nil, ErrParsePrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePrivateKeyString calls ParsePrivateKey([]byte(key)) for all you lazy folk.
|
||||||
|
func ParsePrivateKeyString(block string) (PrivateKey, error) {
|
||||||
|
return ParsePrivateKey([]byte(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrivateKey(der []byte) (PrivateKey, error) {
|
||||||
|
var key PrivateKey
|
||||||
|
|
||||||
|
//fmt.Println("1. ParsePKCS8PrivateKey")
|
||||||
|
xkey, err := x509.ParsePKCS8PrivateKey(der)
|
||||||
|
if nil == err {
|
||||||
|
switch k := xkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
key = k
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
key = k
|
||||||
|
default:
|
||||||
|
err = errors.New("Only RSA and ECDSA (EC) Private Keys are supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
//fmt.Println("2. ParseECPrivateKey")
|
||||||
|
key, err = x509.ParseECPrivateKey(der)
|
||||||
|
if nil != err {
|
||||||
|
//fmt.Println("3. ParsePKCS1PrivateKey")
|
||||||
|
key, err = x509.ParsePKCS1PrivateKey(der)
|
||||||
|
if nil != err {
|
||||||
|
//fmt.Println("4. ParseJWKPrivateKey")
|
||||||
|
key, err = ParseJWKPrivateKey(der)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// But did you know?
|
||||||
|
// You must return nil explicitly for interfaces
|
||||||
|
// https://golang.org/doc/faq#nil_error
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPEMBytes(block []byte) ([][]byte, error) {
|
||||||
|
var pemblock *pem.Block
|
||||||
|
var blocks = make([][]byte, 0, 1)
|
||||||
|
|
||||||
|
// Parse the PEM, if it's a pem
|
||||||
|
for {
|
||||||
|
pemblock, block = pem.Decode(block)
|
||||||
|
if nil != pemblock {
|
||||||
|
// got one block, there may be more
|
||||||
|
blocks = append(blocks, pemblock.Bytes)
|
||||||
|
} else {
|
||||||
|
// the last block was not a PEM block
|
||||||
|
// therefore the next isn't either
|
||||||
|
if 0 != len(block) {
|
||||||
|
blocks = append(blocks, block)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(blocks) > 0 {
|
||||||
|
return blocks, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("no PEM blocks found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePublicKey will try to parse the bytes you give it
|
||||||
|
// in any of the supported formats: PEM, DER, PKIX/SPKI, PKCS1, x509 Certificate, and JWK
|
||||||
|
func ParsePublicKey(block []byte) (PublicKey, error) {
|
||||||
|
blocks, err := getPEMBytes(block)
|
||||||
|
if nil != err {
|
||||||
|
return nil, ErrParsePublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PEM blocks (openssl generates junk metadata blocks for ECs)
|
||||||
|
// or the original DER, or the JWK
|
||||||
|
for i := range blocks {
|
||||||
|
block = blocks[i]
|
||||||
|
if key, err := parsePublicKey(block); nil == err {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range blocks {
|
||||||
|
block = blocks[i]
|
||||||
|
if _, err := parsePrivateKey(block); nil == err {
|
||||||
|
return nil, ErrUnexpectedPrivateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't parse a key arleady, we failed
|
||||||
|
return nil, ErrParsePublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePublicKeyString calls ParsePublicKey([]byte(key)) for all you lazy folk.
|
||||||
|
func ParsePublicKeyString(block string) (PublicKey, error) {
|
||||||
|
return ParsePublicKey([]byte(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePublicKey(der []byte) (PublicKey, error) {
|
||||||
|
cert, err := x509.ParseCertificate(der)
|
||||||
|
if nil == err {
|
||||||
|
switch k := cert.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return NewPublicKey(k), nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return NewPublicKey(k), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("1. ParsePKIXPublicKey")
|
||||||
|
xkey, err := x509.ParsePKIXPublicKey(der)
|
||||||
|
if nil == err {
|
||||||
|
switch k := xkey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return NewPublicKey(k), nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return NewPublicKey(k), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("3. ParsePKCS1PrublicKey")
|
||||||
|
rkey, err := x509.ParsePKCS1PublicKey(der)
|
||||||
|
if nil == err {
|
||||||
|
//fmt.Println("4. ParseJWKPublicKey")
|
||||||
|
return NewPublicKey(rkey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseJWKPublicKey(der)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// But did you know?
|
||||||
|
// You must return nil explicitly for interfaces
|
||||||
|
// https://golang.org/doc/faq#nil_error
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON)
|
||||||
|
func NewJWKPublicKey(m map[string]string) (PublicKey, error) {
|
||||||
|
switch m["kty"] {
|
||||||
|
case "RSA":
|
||||||
|
return parseRSAPublicKey(m)
|
||||||
|
case "EC":
|
||||||
|
return parseECPublicKey(m)
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJWKPublicKey parses a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message
|
||||||
|
func ParseJWKPublicKey(b []byte) (PublicKey, error) {
|
||||||
|
// RSA and EC have "d" as a private part
|
||||||
|
if bytes.Contains(b, []byte(`"d"`)) {
|
||||||
|
return nil, ErrUnexpectedPrivateKey
|
||||||
|
}
|
||||||
|
return newJWKPublicKey(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJWKPublicKeyString calls ParseJWKPublicKey([]byte(key)) for all you lazy folk.
|
||||||
|
func ParseJWKPublicKeyString(s string) (PublicKey, error) {
|
||||||
|
if strings.Contains(s, `"d"`) {
|
||||||
|
return nil, ErrUnexpectedPrivateKey
|
||||||
|
}
|
||||||
|
return newJWKPublicKey(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeJWKPublicKey stream-decodes a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message
|
||||||
|
func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
if err := json.NewDecoder(r).Decode(&m); nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if d := m["d"]; "" != d {
|
||||||
|
return nil, ErrUnexpectedPrivateKey
|
||||||
|
}
|
||||||
|
return newJWKPublicKey(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the underpinnings of the parser as used by the typesafe wrappers
|
||||||
|
func newJWKPublicKey(data interface{}) (PublicKey, error) {
|
||||||
|
var m map[string]string
|
||||||
|
|
||||||
|
switch d := data.(type) {
|
||||||
|
case map[string]string:
|
||||||
|
m = d
|
||||||
|
case string:
|
||||||
|
if err := json.Unmarshal([]byte(d), &m); nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
if err := json.Unmarshal(d, &m); nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Developer Error: unsupported interface type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewJWKPublicKey(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJWKPrivateKey parses a JSON-encoded JWK and returns a PrivateKey, or a (hopefully) helpful error message
|
||||||
|
func ParseJWKPrivateKey(b []byte) (PrivateKey, error) {
|
||||||
|
var m map[string]string
|
||||||
|
if err := json.Unmarshal(b, &m); nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m["kty"] {
|
||||||
|
case "RSA":
|
||||||
|
return parseRSAPrivateKey(m)
|
||||||
|
case "EC":
|
||||||
|
return parseECPrivateKey(m)
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRSAPublicKey(m map[string]string) (*RSAPublicKey, error) {
|
||||||
|
// TODO grab expiry?
|
||||||
|
kid, _ := m["kid"]
|
||||||
|
n, _ := base64.RawURLEncoding.DecodeString(m["n"])
|
||||||
|
e, _ := base64.RawURLEncoding.DecodeString(m["e"])
|
||||||
|
if 0 == len(n) || 0 == len(e) {
|
||||||
|
return nil, ErrParseJWK
|
||||||
|
}
|
||||||
|
ni := &big.Int{}
|
||||||
|
ni.SetBytes(n)
|
||||||
|
ei := &big.Int{}
|
||||||
|
ei.SetBytes(e)
|
||||||
|
|
||||||
|
pub := &rsa.PublicKey{
|
||||||
|
N: ni,
|
||||||
|
E: int(ei.Int64()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RSAPublicKey{
|
||||||
|
PublicKey: pub,
|
||||||
|
KID: kid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRSAPrivateKey(m map[string]string) (key *rsa.PrivateKey, err error) {
|
||||||
|
pub, err := parseRSAPublicKey(m)
|
||||||
|
if nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, _ := base64.RawURLEncoding.DecodeString(m["d"])
|
||||||
|
p, _ := base64.RawURLEncoding.DecodeString(m["p"])
|
||||||
|
q, _ := base64.RawURLEncoding.DecodeString(m["q"])
|
||||||
|
dp, _ := base64.RawURLEncoding.DecodeString(m["dp"])
|
||||||
|
dq, _ := base64.RawURLEncoding.DecodeString(m["dq"])
|
||||||
|
qinv, _ := base64.RawURLEncoding.DecodeString(m["qi"])
|
||||||
|
if 0 == len(d) || 0 == len(p) || 0 == len(dp) || 0 == len(dq) || 0 == len(qinv) {
|
||||||
|
return nil, ErrParseJWK
|
||||||
|
}
|
||||||
|
|
||||||
|
di := &big.Int{}
|
||||||
|
di.SetBytes(d)
|
||||||
|
pi := &big.Int{}
|
||||||
|
pi.SetBytes(p)
|
||||||
|
qi := &big.Int{}
|
||||||
|
qi.SetBytes(q)
|
||||||
|
dpi := &big.Int{}
|
||||||
|
dpi.SetBytes(dp)
|
||||||
|
dqi := &big.Int{}
|
||||||
|
dqi.SetBytes(dq)
|
||||||
|
qinvi := &big.Int{}
|
||||||
|
qinvi.SetBytes(qinv)
|
||||||
|
|
||||||
|
key = &rsa.PrivateKey{
|
||||||
|
PublicKey: *pub.PublicKey,
|
||||||
|
D: di,
|
||||||
|
Primes: []*big.Int{pi, qi},
|
||||||
|
Precomputed: rsa.PrecomputedValues{
|
||||||
|
Dp: dpi,
|
||||||
|
Dq: dqi,
|
||||||
|
Qinv: qinvi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseECPublicKey(m map[string]string) (*ECPublicKey, error) {
|
||||||
|
// TODO grab expiry?
|
||||||
|
kid, _ := m["kid"]
|
||||||
|
x, _ := base64.RawURLEncoding.DecodeString(m["x"])
|
||||||
|
y, _ := base64.RawURLEncoding.DecodeString(m["y"])
|
||||||
|
if 0 == len(x) || 0 == len(y) || 0 == len(m["crv"]) {
|
||||||
|
return nil, ErrParseJWK
|
||||||
|
}
|
||||||
|
|
||||||
|
xi := &big.Int{}
|
||||||
|
xi.SetBytes(x)
|
||||||
|
|
||||||
|
yi := &big.Int{}
|
||||||
|
yi.SetBytes(y)
|
||||||
|
|
||||||
|
var crv elliptic.Curve
|
||||||
|
switch m["crv"] {
|
||||||
|
case "P-256":
|
||||||
|
crv = elliptic.P256()
|
||||||
|
case "P-384":
|
||||||
|
crv = elliptic.P384()
|
||||||
|
case "P-521":
|
||||||
|
crv = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidCurve
|
||||||
|
}
|
||||||
|
|
||||||
|
pub := &ecdsa.PublicKey{
|
||||||
|
Curve: crv,
|
||||||
|
X: xi,
|
||||||
|
Y: yi,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ECPublicKey{
|
||||||
|
PublicKey: pub,
|
||||||
|
KID: kid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseECPrivateKey(m map[string]string) (*ecdsa.PrivateKey, error) {
|
||||||
|
pub, err := parseECPublicKey(m)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, _ := base64.RawURLEncoding.DecodeString(m["d"])
|
||||||
|
if 0 == len(d) {
|
||||||
|
return nil, ErrParseJWK
|
||||||
|
}
|
||||||
|
di := &big.Int{}
|
||||||
|
di.SetBytes(d)
|
||||||
|
|
||||||
|
return &ecdsa.PrivateKey{
|
||||||
|
PublicKey: *pub.PublicKey,
|
||||||
|
D: di,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
mathrand "math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalPEMPublicKey outputs the given public key as JWK
|
||||||
|
func MarshalPEMPublicKey(pubkey crypto.PublicKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPublicKey(pubkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalDERPublicKey outputs the given public key as JWK
|
||||||
|
func MarshalDERPublicKey(pubkey crypto.PublicKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPublicKey(pubkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return block.Bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalDERPublicKey outputs the given public key as JWK
|
||||||
|
func marshalDERPublicKey(pubkey crypto.PublicKey) (*pem.Block, error) {
|
||||||
|
|
||||||
|
var der []byte
|
||||||
|
var typ string
|
||||||
|
var err error
|
||||||
|
switch k := pubkey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
der = x509.MarshalPKCS1PublicKey(k)
|
||||||
|
typ = "RSA PUBLIC KEY"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
typ = "PUBLIC KEY"
|
||||||
|
der, err = x509.MarshalPKIXPublicKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Developer Error: impossible key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pem.Block{
|
||||||
|
Bytes: der,
|
||||||
|
Type: typ,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJWKPrivateKey outputs the given private key as JWK
|
||||||
|
func MarshalJWKPrivateKey(privkey PrivateKey) []byte {
|
||||||
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return MarshalRSAPrivateKey(k)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return MarshalECPrivateKey(k)
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalDERPrivateKey outputs the given private key as ASN.1 DER
|
||||||
|
func MarshalDERPrivateKey(privkey PrivateKey) ([]byte, error) {
|
||||||
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return x509.MarshalPKCS1PrivateKey(k), nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return x509.MarshalECPrivateKey(k)
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalDERPrivateKey(privkey PrivateKey) (*pem.Block, error) {
|
||||||
|
var typ string
|
||||||
|
var bytes []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
if 0 == mathrand.Intn(2) {
|
||||||
|
typ = "PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalPKCS8PrivateKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typ = "RSA PRIVATE KEY"
|
||||||
|
bytes = x509.MarshalPKCS1PrivateKey(k)
|
||||||
|
}
|
||||||
|
return &pem.Block{
|
||||||
|
Type: typ,
|
||||||
|
Bytes: bytes,
|
||||||
|
}, nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if 0 == mathrand.Intn(2) {
|
||||||
|
typ = "PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalPKCS8PrivateKey(k)
|
||||||
|
} else {
|
||||||
|
typ = "EC PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalECPrivateKey(k)
|
||||||
|
}
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pem.Block{
|
||||||
|
Type: typ,
|
||||||
|
Bytes: bytes,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalPEMPrivateKey outputs the given private key as ASN.1 PEM
|
||||||
|
func MarshalPEMPrivateKey(privkey PrivateKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPrivateKey(privkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalECPrivateKey will output the given private key as JWK
|
||||||
|
func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte {
|
||||||
|
crv := k.Curve.Params().Name
|
||||||
|
d := base64.RawURLEncoding.EncodeToString(k.D.Bytes())
|
||||||
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
||||||
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(
|
||||||
|
`{"crv":%q,"d":%q,"kty":"EC","x":%q,"y":%q}`,
|
||||||
|
crv, d, x, y,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalRSAPrivateKey will output the given private key as JWK
|
||||||
|
func MarshalRSAPrivateKey(pk *rsa.PrivateKey) []byte {
|
||||||
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pk.E)).Bytes())
|
||||||
|
n := base64.RawURLEncoding.EncodeToString(pk.N.Bytes())
|
||||||
|
d := base64.RawURLEncoding.EncodeToString(pk.D.Bytes())
|
||||||
|
p := base64.RawURLEncoding.EncodeToString(pk.Primes[0].Bytes())
|
||||||
|
q := base64.RawURLEncoding.EncodeToString(pk.Primes[1].Bytes())
|
||||||
|
dp := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dp.Bytes())
|
||||||
|
dq := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dq.Bytes())
|
||||||
|
qi := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Qinv.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(
|
||||||
|
`{"d":%q,"dp":%q,"dq":%q,"e":%q,"kty":"RSA","n":%q,"p":%q,"q":%q,"qi":%q}`,
|
||||||
|
d, dp, dq, e, n, p, q, qi,
|
||||||
|
))
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
mathrand "math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this shananigans is only for testing and debug API stuff
|
||||||
|
func (o *keyOptions) maybeMockReader() io.Reader {
|
||||||
|
if !allowMocking {
|
||||||
|
panic("mock method called when mocking is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 == o.mockSeed {
|
||||||
|
return randReader
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("WARNING: MOCK: using insecure reader")
|
||||||
|
return mathrand.New(mathrand.NewSource(o.mockSeed))
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxRetry = 16
|
||||||
|
|
||||||
|
func maybeDerandomizeMockKey(privkey PrivateKey, keylen int, opts *keyOptions) PrivateKey {
|
||||||
|
if 0 != opts.mockSeed {
|
||||||
|
for i := 0; i < maxRetry; i++ {
|
||||||
|
otherkey, _ := rsa.GenerateKey(opts.nextReader(), keylen)
|
||||||
|
otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D)
|
||||||
|
if 0 != otherCmp {
|
||||||
|
// There are two possible keys, choose the lesser D value
|
||||||
|
// See https://github.com/square/go-jose/issues/189
|
||||||
|
if otherCmp < 0 {
|
||||||
|
privkey = otherkey
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if maxRetry == i-1 {
|
||||||
|
log.Printf("error: coinflip landed on heads %d times", maxRetry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return privkey
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
mathrand "math/rand" // to be used for good, not evil
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object is a type alias representing generic JSON data
|
||||||
|
type Object = map[string]interface{}
|
||||||
|
|
||||||
|
// SignClaims adds `typ`, `kid` (or `jwk`), and `alg` in the header and expects claims for `jti`, `exp`, `iss`, and `iat`
|
||||||
|
func SignClaims(privkey PrivateKey, header Object, claims Object) (*JWS, error) {
|
||||||
|
var randsrc io.Reader = randReader
|
||||||
|
seed, _ := header["_seed"].(int64)
|
||||||
|
if 0 != seed {
|
||||||
|
randsrc = mathrand.New(mathrand.NewSource(seed))
|
||||||
|
//delete(header, "_seed")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected, header, err := headerToProtected(NewPublicKey(privkey.Public()), header)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
protected64 := base64.RawURLEncoding.EncodeToString(protected)
|
||||||
|
|
||||||
|
payload, err := claimsToPayload(claims)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payload64 := base64.RawURLEncoding.EncodeToString(payload)
|
||||||
|
|
||||||
|
signable := fmt.Sprintf(`%s.%s`, protected64, payload64)
|
||||||
|
hash := sha256.Sum256([]byte(signable))
|
||||||
|
|
||||||
|
sig := Sign(privkey, hash[:], randsrc)
|
||||||
|
sig64 := base64.RawURLEncoding.EncodeToString(sig)
|
||||||
|
//log.Printf("\n(Sign)\nSignable: %s", signable)
|
||||||
|
//log.Printf("Hash: %s", hash)
|
||||||
|
//log.Printf("Sig: %s", sig64)
|
||||||
|
|
||||||
|
return &JWS{
|
||||||
|
Header: header,
|
||||||
|
Claims: claims,
|
||||||
|
Protected: protected64,
|
||||||
|
Payload: payload64,
|
||||||
|
Signature: sig64,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) {
|
||||||
|
if nil == header {
|
||||||
|
header = Object{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only supporting 2048-bit and P256 keys right now
|
||||||
|
// because that's all that's practical and well-supported.
|
||||||
|
// No security theatre here.
|
||||||
|
alg := "ES256"
|
||||||
|
switch pub.Key().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
alg = "RS256"
|
||||||
|
}
|
||||||
|
|
||||||
|
if selfSign, _ := header["_jwk"].(bool); selfSign {
|
||||||
|
delete(header, "_jwk")
|
||||||
|
any := Object{}
|
||||||
|
_ = json.Unmarshal(MarshalJWKPublicKey(pub), &any)
|
||||||
|
header["jwk"] = any
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO what are the acceptable values? JWT. JWS? others?
|
||||||
|
header["typ"] = "JWT"
|
||||||
|
if _, ok := header["jwk"]; !ok {
|
||||||
|
thumbprint := ThumbprintPublicKey(pub)
|
||||||
|
kid, _ := header["kid"].(string)
|
||||||
|
if "" != kid && thumbprint != kid {
|
||||||
|
return nil, nil, errors.New("'kid' should be the key's thumbprint")
|
||||||
|
}
|
||||||
|
header["kid"] = thumbprint
|
||||||
|
}
|
||||||
|
header["alg"] = alg
|
||||||
|
|
||||||
|
protected, err := json.Marshal(header)
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return protected, header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func claimsToPayload(claims Object) ([]byte, error) {
|
||||||
|
if nil == claims {
|
||||||
|
claims = Object{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dur time.Duration
|
||||||
|
jti, _ := claims["jti"].(string)
|
||||||
|
insecure, _ := claims["insecure"].(bool)
|
||||||
|
|
||||||
|
switch exp := claims["exp"].(type) {
|
||||||
|
case time.Duration:
|
||||||
|
// TODO: MUST this go first?
|
||||||
|
// int64(time.Duration) vs time.Duration(int64)
|
||||||
|
dur = exp
|
||||||
|
case string:
|
||||||
|
var err error
|
||||||
|
dur, err = time.ParseDuration(exp)
|
||||||
|
// TODO s, err := time.ParseDuration(dur)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
dur = time.Second * time.Duration(exp)
|
||||||
|
case int64:
|
||||||
|
dur = time.Second * time.Duration(exp)
|
||||||
|
case float64:
|
||||||
|
dur = time.Second * time.Duration(exp)
|
||||||
|
default:
|
||||||
|
dur = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" == jti && 0 == dur && !insecure {
|
||||||
|
return nil, errors.New("token must have jti or exp as to be expirable / cancellable")
|
||||||
|
}
|
||||||
|
claims["exp"] = time.Now().Add(dur).Unix()
|
||||||
|
|
||||||
|
return json.Marshal(claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs both RSA and ECDSA. Use `nil` or `crypto/rand.Reader` except for debugging.
|
||||||
|
func Sign(privkey PrivateKey, hash []byte, rand io.Reader) []byte {
|
||||||
|
if nil == rand {
|
||||||
|
rand = randReader
|
||||||
|
}
|
||||||
|
var sig []byte
|
||||||
|
|
||||||
|
if len(hash) != 32 {
|
||||||
|
panic("only 256-bit hashes for 2048-bit and 256-bit keys are supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
sig, _ = rsa.SignPKCS1v15(rand, k, crypto.SHA256, hash)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
r, s, _ := ecdsa.Sign(rand, k, hash[:])
|
||||||
|
rb := r.Bytes()
|
||||||
|
for len(rb) < 32 {
|
||||||
|
rb = append([]byte{0}, rb...)
|
||||||
|
}
|
||||||
|
sb := s.Bytes()
|
||||||
|
for len(rb) < 32 {
|
||||||
|
sb = append([]byte{0}, sb...)
|
||||||
|
}
|
||||||
|
sig = append(rb, sb...)
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyClaims will check the signature of a parsed JWT
|
||||||
|
func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) {
|
||||||
|
kid, _ := jws.Header["kid"].(string)
|
||||||
|
jwkmap, hasJWK := jws.Header["jwk"].(Object)
|
||||||
|
//var jwk JWK = nil
|
||||||
|
|
||||||
|
seed, _ := jws.Header["_seed"].(int64)
|
||||||
|
seedf64, _ := jws.Header["_seed"].(float64)
|
||||||
|
kty, _ := jws.Header["_kty"].(string)
|
||||||
|
if 0 == seed {
|
||||||
|
seed = int64(seedf64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pub PublicKey = nil
|
||||||
|
if hasJWK {
|
||||||
|
pub, errs = selfsignCheck(jwkmap, errs)
|
||||||
|
} else {
|
||||||
|
opts := &keyOptions{mockSeed: seed, KeyType: kty}
|
||||||
|
pub, errs = pubkeyCheck(pubkey, kid, opts, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
jti, _ := jws.Claims["jti"].(string)
|
||||||
|
expf64, _ := jws.Claims["exp"].(float64)
|
||||||
|
exp := int64(expf64)
|
||||||
|
if 0 == exp {
|
||||||
|
if "" == jti {
|
||||||
|
err := errors.New("one of 'jti' or 'exp' must exist for token expiry")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if time.Now().Unix() > exp {
|
||||||
|
err := fmt.Errorf("token expired at %d (%s)", exp, time.Unix(exp, 0))
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signable := fmt.Sprintf("%s.%s", jws.Protected, jws.Payload)
|
||||||
|
hash := sha256.Sum256([]byte(signable))
|
||||||
|
sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
|
||||||
|
if nil != err {
|
||||||
|
err := fmt.Errorf("could not decode signature: %w", err)
|
||||||
|
errs = append(errs, err)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("\n(Verify)\nSignable: %s", signable)
|
||||||
|
//log.Printf("Hash: %s", hash)
|
||||||
|
//log.Printf("Sig: %s", jws.Signature)
|
||||||
|
if nil == pub {
|
||||||
|
err := fmt.Errorf("token signature could not be verified")
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else if !Verify(pub, hash[:], sig) {
|
||||||
|
err := fmt.Errorf("token signature is not valid")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) {
|
||||||
|
var pub PublicKey = nil
|
||||||
|
log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk'")
|
||||||
|
log.Println("Security TODO: did not check jws.Claims[\"iss\"]")
|
||||||
|
kty := jwkmap["kty"]
|
||||||
|
var err error
|
||||||
|
if "RSA" == kty {
|
||||||
|
e, _ := jwkmap["e"].(string)
|
||||||
|
n, _ := jwkmap["n"].(string)
|
||||||
|
k, _ := (&RSAJWK{
|
||||||
|
Exp: e,
|
||||||
|
N: n,
|
||||||
|
}).marshalJWK()
|
||||||
|
pub, err = ParseJWKPublicKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, append(errs, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crv, _ := jwkmap["crv"].(string)
|
||||||
|
x, _ := jwkmap["x"].(string)
|
||||||
|
y, _ := jwkmap["y"].(string)
|
||||||
|
k, _ := (&ECJWK{
|
||||||
|
Curve: crv,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}).marshalJWK()
|
||||||
|
pub, err = ParseJWKPublicKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pub, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) (PublicKey, []error) {
|
||||||
|
var pub PublicKey = nil
|
||||||
|
|
||||||
|
if "" == kid {
|
||||||
|
err := errors.New("token should have 'kid' or 'jwk' in header to identify the public key")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil == pubkey {
|
||||||
|
if allowMocking {
|
||||||
|
if 0 == opts.mockSeed {
|
||||||
|
err := errors.New("the debug API requires '_seed' to accompany 'kid'")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if "" == opts.KeyType {
|
||||||
|
err := errors.New("the debug API requires '_kty' to accompany '_seed'")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 == opts.mockSeed || "" == opts.KeyType {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
privkey := newPrivateKey(opts)
|
||||||
|
pub = NewPublicKey(privkey.Public())
|
||||||
|
return pub, errs
|
||||||
|
}
|
||||||
|
err := errors.New("no matching public key")
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
pub = pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil != pub && "" != kid {
|
||||||
|
if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(pub.Thumbprint())) {
|
||||||
|
err := errors.New("'kid' does not match the public key thumbprint")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pub, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify will check the signature of a hash
|
||||||
|
func Verify(pubkey PublicKey, hash []byte, sig []byte) bool {
|
||||||
|
|
||||||
|
switch pub := pubkey.Key().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
//log.Printf("RSA VERIFY")
|
||||||
|
// TODO Size(key) to detect key size ?
|
||||||
|
//alg := "SHA256"
|
||||||
|
// TODO: this hasn't been tested yet
|
||||||
|
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
r := &big.Int{}
|
||||||
|
r.SetBytes(sig[0:32])
|
||||||
|
s := &big.Int{}
|
||||||
|
s.SetBytes(sig[32:])
|
||||||
|
return ecdsa.Verify(pub, hash, r, s)
|
||||||
|
default:
|
||||||
|
panic("impossible condition: non-rsa/non-ecdsa key")
|
||||||
|
//return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google 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,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,388 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package semver implements comparison of semantic version strings.
|
||||||
|
// In this package, semantic version strings must begin with a leading "v",
|
||||||
|
// as in "v1.0.0".
|
||||||
|
//
|
||||||
|
// The general form of a semantic version string accepted by this package is
|
||||||
|
//
|
||||||
|
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||||
|
//
|
||||||
|
// where square brackets indicate optional parts of the syntax;
|
||||||
|
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||||
|
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||||
|
// using only alphanumeric characters and hyphens; and
|
||||||
|
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||||
|
//
|
||||||
|
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||||
|
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||||
|
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||||
|
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||||
|
package semver
|
||||||
|
|
||||||
|
// parsed returns the parsed form of a semantic version string.
|
||||||
|
type parsed struct {
|
||||||
|
major string
|
||||||
|
minor string
|
||||||
|
patch string
|
||||||
|
short string
|
||||||
|
prerelease string
|
||||||
|
build string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether v is a valid semantic version string.
|
||||||
|
func IsValid(v string) bool {
|
||||||
|
_, ok := parse(v)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical returns the canonical formatting of the semantic version v.
|
||||||
|
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||||
|
// Two semantic versions compare equal only if their canonical formattings
|
||||||
|
// are identical strings.
|
||||||
|
// The canonical invalid semantic version is the empty string.
|
||||||
|
func Canonical(v string) string {
|
||||||
|
p, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if p.build != "" {
|
||||||
|
return v[:len(v)-len(p.build)]
|
||||||
|
}
|
||||||
|
if p.short != "" {
|
||||||
|
return v + p.short
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Major returns the major version prefix of the semantic version v.
|
||||||
|
// For example, Major("v2.1.0") == "v2".
|
||||||
|
// If v is an invalid semantic version string, Major returns the empty string.
|
||||||
|
func Major(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v[:1+len(pv.major)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||||
|
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||||
|
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||||
|
func MajorMinor(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
i := 1 + len(pv.major)
|
||||||
|
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||||
|
return v[:j]
|
||||||
|
}
|
||||||
|
return v[:i] + "." + pv.minor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||||
|
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||||
|
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||||
|
func Prerelease(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build returns the build suffix of the semantic version v.
|
||||||
|
// For example, Build("v2.1.0+meta") == "+meta".
|
||||||
|
// If v is an invalid semantic version string, Build returns the empty string.
|
||||||
|
func Build(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.build
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare returns an integer comparing two versions according to
|
||||||
|
// semantic version precedence.
|
||||||
|
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||||
|
//
|
||||||
|
// An invalid semantic version string is considered less than a valid one.
|
||||||
|
// All invalid semantic version strings compare equal to each other.
|
||||||
|
func Compare(v, w string) int {
|
||||||
|
pv, ok1 := parse(v)
|
||||||
|
pw, ok2 := parse(w)
|
||||||
|
if !ok1 && !ok2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if !ok1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if !ok2 {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max canonicalizes its arguments and then returns the version string
|
||||||
|
// that compares greater.
|
||||||
|
func Max(v, w string) string {
|
||||||
|
v = Canonical(v)
|
||||||
|
w = Canonical(w)
|
||||||
|
if Compare(v, w) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(v string) (p parsed, ok bool) {
|
||||||
|
if v == "" || v[0] != 'v' {
|
||||||
|
p.err = "missing v prefix"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.major, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad major version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.minor = "0"
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0.0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad minor prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.minor, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad minor version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad patch prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.patch, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad patch version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '-' {
|
||||||
|
p.prerelease, v, ok = parsePrerelease(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad prerelease"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '+' {
|
||||||
|
p.build, v, ok = parseBuild(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad build"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v != "" {
|
||||||
|
p.err = "junk on end"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] < '0' || '9' < v[0] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if v[0] == '0' && i != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||||
|
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||||
|
// a series of dot separated identifiers immediately following the patch version.
|
||||||
|
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||||
|
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||||
|
if v == "" || v[0] != '-' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) && v[i] != '+' {
|
||||||
|
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBuild(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" || v[0] != '+' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) {
|
||||||
|
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentChar(c byte) bool {
|
||||||
|
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBadNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v) && i > 1 && v[0] == '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareInt(x, y string) int {
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if len(x) < len(y) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(x) > len(y) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if x < y {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePrerelease(x, y string) int {
|
||||||
|
// "When major, minor, and patch are equal, a pre-release version has
|
||||||
|
// lower precedence than a normal version.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0.
|
||||||
|
// Precedence for two pre-release versions with the same major, minor,
|
||||||
|
// and patch version MUST be determined by comparing each dot separated
|
||||||
|
// identifier from left to right until a difference is found as follows:
|
||||||
|
// identifiers consisting of only digits are compared numerically and
|
||||||
|
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||||
|
// sort order. Numeric identifiers always have lower precedence than
|
||||||
|
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||||
|
// higher precedence than a smaller set, if all of the preceding
|
||||||
|
// identifiers are equal.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||||
|
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if y == "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for x != "" && y != "" {
|
||||||
|
x = x[1:] // skip - or .
|
||||||
|
y = y[1:] // skip - or .
|
||||||
|
var dx, dy string
|
||||||
|
dx, x = nextIdent(x)
|
||||||
|
dy, y = nextIdent(y)
|
||||||
|
if dx != dy {
|
||||||
|
ix := isNum(dx)
|
||||||
|
iy := isNum(dy)
|
||||||
|
if ix != iy {
|
||||||
|
if ix {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ix {
|
||||||
|
if len(dx) < len(dy) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(dx) > len(dy) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dx < dy {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextIdent(x string) (dx, rest string) {
|
||||||
|
i := 0
|
||||||
|
for i < len(x) && x[i] != '.' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return x[:i], x[i:]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google 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,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,655 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer
|
||||||
|
// interface. Given the name of a (signed or unsigned) integer type T that has constants
|
||||||
|
// defined, stringer will create a new self-contained Go source file implementing
|
||||||
|
// func (t T) String() string
|
||||||
|
// The file is created in the same package and directory as the package that defines T.
|
||||||
|
// It has helpful defaults designed for use with go generate.
|
||||||
|
//
|
||||||
|
// Stringer works best with constants that are consecutive values such as created using iota,
|
||||||
|
// but creates good code regardless. In the future it might also provide custom support for
|
||||||
|
// constant sets that are bit patterns.
|
||||||
|
//
|
||||||
|
// For example, given this snippet,
|
||||||
|
//
|
||||||
|
// package painkiller
|
||||||
|
//
|
||||||
|
// type Pill int
|
||||||
|
//
|
||||||
|
// const (
|
||||||
|
// Placebo Pill = iota
|
||||||
|
// Aspirin
|
||||||
|
// Ibuprofen
|
||||||
|
// Paracetamol
|
||||||
|
// Acetaminophen = Paracetamol
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// running this command
|
||||||
|
//
|
||||||
|
// stringer -type=Pill
|
||||||
|
//
|
||||||
|
// in the same directory will create the file pill_string.go, in package painkiller,
|
||||||
|
// containing a definition of
|
||||||
|
//
|
||||||
|
// func (Pill) String() string
|
||||||
|
//
|
||||||
|
// That method will translate the value of a Pill constant to the string representation
|
||||||
|
// of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will
|
||||||
|
// print the string "Aspirin".
|
||||||
|
//
|
||||||
|
// Typically this process would be run using go generate, like this:
|
||||||
|
//
|
||||||
|
// //go:generate stringer -type=Pill
|
||||||
|
//
|
||||||
|
// If multiple constants have the same value, the lexically first matching name will
|
||||||
|
// be used (in the example, Acetaminophen will print as "Paracetamol").
|
||||||
|
//
|
||||||
|
// With no arguments, it processes the package in the current directory.
|
||||||
|
// Otherwise, the arguments must name a single directory holding a Go package
|
||||||
|
// or a set of Go source files that represent a single Go package.
|
||||||
|
//
|
||||||
|
// The -type flag accepts a comma-separated list of types so a single run can
|
||||||
|
// generate methods for multiple types. The default output file is t_string.go,
|
||||||
|
// where t is the lower-cased name of the first type listed. It can be overridden
|
||||||
|
// with the -output flag.
|
||||||
|
//
|
||||||
|
// The -linecomment flag tells stringer to generate the text of any line comment, trimmed
|
||||||
|
// of leading spaces, instead of the constant name. For instance, if the constants above had a
|
||||||
|
// Pill prefix, one could write
|
||||||
|
//
|
||||||
|
// PillAspirin // Aspirin
|
||||||
|
//
|
||||||
|
// to suppress it in the output.
|
||||||
|
package main // import "golang.org/x/tools/cmd/stringer"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
|
||||||
|
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
|
||||||
|
trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
|
||||||
|
linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
|
||||||
|
buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Usage is a replacement usage function for the flags package.
|
||||||
|
func Usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of stringer:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "For more information, see:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/stringer\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("stringer: ")
|
||||||
|
flag.Usage = Usage
|
||||||
|
flag.Parse()
|
||||||
|
if len(*typeNames) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
types := strings.Split(*typeNames, ",")
|
||||||
|
var tags []string
|
||||||
|
if len(*buildTags) > 0 {
|
||||||
|
tags = strings.Split(*buildTags, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We accept either one directory or a list of files. Which do we have?
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
// Default: process whole package in current directory.
|
||||||
|
args = []string{"."}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the package once.
|
||||||
|
var dir string
|
||||||
|
g := Generator{
|
||||||
|
trimPrefix: *trimprefix,
|
||||||
|
lineComment: *linecomment,
|
||||||
|
}
|
||||||
|
// TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc).
|
||||||
|
if len(args) == 1 && isDirectory(args[0]) {
|
||||||
|
dir = args[0]
|
||||||
|
} else {
|
||||||
|
if len(tags) != 0 {
|
||||||
|
log.Fatal("-tags option applies only to directories, not when files are specified")
|
||||||
|
}
|
||||||
|
dir = filepath.Dir(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
g.parsePackage(args, tags)
|
||||||
|
|
||||||
|
// Print the header and package clause.
|
||||||
|
g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
|
||||||
|
g.Printf("\n")
|
||||||
|
g.Printf("package %s", g.pkg.name)
|
||||||
|
g.Printf("\n")
|
||||||
|
g.Printf("import \"strconv\"\n") // Used by all methods.
|
||||||
|
|
||||||
|
// Run generate for each type.
|
||||||
|
for _, typeName := range types {
|
||||||
|
g.generate(typeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the output.
|
||||||
|
src := g.format()
|
||||||
|
|
||||||
|
// Write to file.
|
||||||
|
outputName := *output
|
||||||
|
if outputName == "" {
|
||||||
|
baseName := fmt.Sprintf("%s_string.go", types[0])
|
||||||
|
outputName = filepath.Join(dir, strings.ToLower(baseName))
|
||||||
|
}
|
||||||
|
err := ioutil.WriteFile(outputName, src, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("writing output: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDirectory reports whether the named file is a directory.
|
||||||
|
func isDirectory(name string) bool {
|
||||||
|
info, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generator holds the state of the analysis. Primarily used to buffer
|
||||||
|
// the output for format.Source.
|
||||||
|
type Generator struct {
|
||||||
|
buf bytes.Buffer // Accumulated output.
|
||||||
|
pkg *Package // Package we are scanning.
|
||||||
|
|
||||||
|
trimPrefix string
|
||||||
|
lineComment bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) Printf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(&g.buf, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File holds a single parsed file and associated data.
|
||||||
|
type File struct {
|
||||||
|
pkg *Package // Package to which this file belongs.
|
||||||
|
file *ast.File // Parsed AST.
|
||||||
|
// These fields are reset for each type being generated.
|
||||||
|
typeName string // Name of the constant type.
|
||||||
|
values []Value // Accumulator for constant values of that type.
|
||||||
|
|
||||||
|
trimPrefix string
|
||||||
|
lineComment bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
name string
|
||||||
|
defs map[*ast.Ident]types.Object
|
||||||
|
files []*File
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePackage analyzes the single package constructed from the patterns and tags.
|
||||||
|
// parsePackage exits if there is an error.
|
||||||
|
func (g *Generator) parsePackage(patterns []string, tags []string) {
|
||||||
|
cfg := &packages.Config{
|
||||||
|
Mode: packages.LoadSyntax,
|
||||||
|
// TODO: Need to think about constants in test files. Maybe write type_string_test.go
|
||||||
|
// in a separate pass? For later.
|
||||||
|
Tests: false,
|
||||||
|
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
|
||||||
|
}
|
||||||
|
pkgs, err := packages.Load(cfg, patterns...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(pkgs) != 1 {
|
||||||
|
log.Fatalf("error: %d packages found", len(pkgs))
|
||||||
|
}
|
||||||
|
g.addPackage(pkgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// addPackage adds a type checked Package and its syntax files to the generator.
|
||||||
|
func (g *Generator) addPackage(pkg *packages.Package) {
|
||||||
|
g.pkg = &Package{
|
||||||
|
name: pkg.Name,
|
||||||
|
defs: pkg.TypesInfo.Defs,
|
||||||
|
files: make([]*File, len(pkg.Syntax)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, file := range pkg.Syntax {
|
||||||
|
g.pkg.files[i] = &File{
|
||||||
|
file: file,
|
||||||
|
pkg: g.pkg,
|
||||||
|
trimPrefix: g.trimPrefix,
|
||||||
|
lineComment: g.lineComment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate produces the String method for the named type.
|
||||||
|
func (g *Generator) generate(typeName string) {
|
||||||
|
values := make([]Value, 0, 100)
|
||||||
|
for _, file := range g.pkg.files {
|
||||||
|
// Set the state for this run of the walker.
|
||||||
|
file.typeName = typeName
|
||||||
|
file.values = nil
|
||||||
|
if file.file != nil {
|
||||||
|
ast.Inspect(file.file, file.genDecl)
|
||||||
|
values = append(values, file.values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(values) == 0 {
|
||||||
|
log.Fatalf("no values defined for type %s", typeName)
|
||||||
|
}
|
||||||
|
// Generate code that will fail if the constants change value.
|
||||||
|
g.Printf("func _() {\n")
|
||||||
|
g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")
|
||||||
|
g.Printf("\t// Re-run the stringer command to generate them again.\n")
|
||||||
|
g.Printf("\tvar x [1]struct{}\n")
|
||||||
|
for _, v := range values {
|
||||||
|
g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str)
|
||||||
|
}
|
||||||
|
g.Printf("}\n")
|
||||||
|
runs := splitIntoRuns(values)
|
||||||
|
// The decision of which pattern to use depends on the number of
|
||||||
|
// runs in the numbers. If there's only one, it's easy. For more than
|
||||||
|
// one, there's a tradeoff between complexity and size of the data
|
||||||
|
// and code vs. the simplicity of a map. A map takes more space,
|
||||||
|
// but so does the code. The decision here (crossover at 10) is
|
||||||
|
// arbitrary, but considers that for large numbers of runs the cost
|
||||||
|
// of the linear scan in the switch might become important, and
|
||||||
|
// rather than use yet another algorithm such as binary search,
|
||||||
|
// we punt and use a map. In any case, the likelihood of a map
|
||||||
|
// being necessary for any realistic example other than bitmasks
|
||||||
|
// is very low. And bitmasks probably deserve their own analysis,
|
||||||
|
// to be done some other day.
|
||||||
|
switch {
|
||||||
|
case len(runs) == 1:
|
||||||
|
g.buildOneRun(runs, typeName)
|
||||||
|
case len(runs) <= 10:
|
||||||
|
g.buildMultipleRuns(runs, typeName)
|
||||||
|
default:
|
||||||
|
g.buildMap(runs, typeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitIntoRuns breaks the values into runs of contiguous sequences.
|
||||||
|
// For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}.
|
||||||
|
// The input slice is known to be non-empty.
|
||||||
|
func splitIntoRuns(values []Value) [][]Value {
|
||||||
|
// We use stable sort so the lexically first name is chosen for equal elements.
|
||||||
|
sort.Stable(byValue(values))
|
||||||
|
// Remove duplicates. Stable sort has put the one we want to print first,
|
||||||
|
// so use that one. The String method won't care about which named constant
|
||||||
|
// was the argument, so the first name for the given value is the only one to keep.
|
||||||
|
// We need to do this because identical values would cause the switch or map
|
||||||
|
// to fail to compile.
|
||||||
|
j := 1
|
||||||
|
for i := 1; i < len(values); i++ {
|
||||||
|
if values[i].value != values[i-1].value {
|
||||||
|
values[j] = values[i]
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values = values[:j]
|
||||||
|
runs := make([][]Value, 0, 10)
|
||||||
|
for len(values) > 0 {
|
||||||
|
// One contiguous sequence per outer loop.
|
||||||
|
i := 1
|
||||||
|
for i < len(values) && values[i].value == values[i-1].value+1 {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
runs = append(runs, values[:i])
|
||||||
|
values = values[i:]
|
||||||
|
}
|
||||||
|
return runs
|
||||||
|
}
|
||||||
|
|
||||||
|
// format returns the gofmt-ed contents of the Generator's buffer.
|
||||||
|
func (g *Generator) format() []byte {
|
||||||
|
src, err := format.Source(g.buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, but can arise when developing this code.
|
||||||
|
// The user can compile the output to see the error.
|
||||||
|
log.Printf("warning: internal error: invalid Go generated: %s", err)
|
||||||
|
log.Printf("warning: compile the package to analyze the error")
|
||||||
|
return g.buf.Bytes()
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value represents a declared constant.
|
||||||
|
type Value struct {
|
||||||
|
originalName string // The name of the constant.
|
||||||
|
name string // The name with trimmed prefix.
|
||||||
|
// The value is stored as a bit pattern alone. The boolean tells us
|
||||||
|
// whether to interpret it as an int64 or a uint64; the only place
|
||||||
|
// this matters is when sorting.
|
||||||
|
// Much of the time the str field is all we need; it is printed
|
||||||
|
// by Value.String.
|
||||||
|
value uint64 // Will be converted to int64 when needed.
|
||||||
|
signed bool // Whether the constant is a signed type.
|
||||||
|
str string // The string representation given by the "go/constant" package.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Value) String() string {
|
||||||
|
return v.str
|
||||||
|
}
|
||||||
|
|
||||||
|
// byValue lets us sort the constants into increasing order.
|
||||||
|
// We take care in the Less method to sort in signed or unsigned order,
|
||||||
|
// as appropriate.
|
||||||
|
type byValue []Value
|
||||||
|
|
||||||
|
func (b byValue) Len() int { return len(b) }
|
||||||
|
func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||||
|
func (b byValue) Less(i, j int) bool {
|
||||||
|
if b[i].signed {
|
||||||
|
return int64(b[i].value) < int64(b[j].value)
|
||||||
|
}
|
||||||
|
return b[i].value < b[j].value
|
||||||
|
}
|
||||||
|
|
||||||
|
// genDecl processes one declaration clause.
|
||||||
|
func (f *File) genDecl(node ast.Node) bool {
|
||||||
|
decl, ok := node.(*ast.GenDecl)
|
||||||
|
if !ok || decl.Tok != token.CONST {
|
||||||
|
// We only care about const declarations.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// The name of the type of the constants we are declaring.
|
||||||
|
// Can change if this is a multi-element declaration.
|
||||||
|
typ := ""
|
||||||
|
// Loop over the elements of the declaration. Each element is a ValueSpec:
|
||||||
|
// a list of names possibly followed by a type, possibly followed by values.
|
||||||
|
// If the type and value are both missing, we carry down the type (and value,
|
||||||
|
// but the "go/types" package takes care of that).
|
||||||
|
for _, spec := range decl.Specs {
|
||||||
|
vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
|
||||||
|
if vspec.Type == nil && len(vspec.Values) > 0 {
|
||||||
|
// "X = 1". With no type but a value. If the constant is untyped,
|
||||||
|
// skip this vspec and reset the remembered type.
|
||||||
|
typ = ""
|
||||||
|
|
||||||
|
// If this is a simple type conversion, remember the type.
|
||||||
|
// We don't mind if this is actually a call; a qualified call won't
|
||||||
|
// be matched (that will be SelectorExpr, not Ident), and only unusual
|
||||||
|
// situations will result in a function call that appears to be
|
||||||
|
// a type conversion.
|
||||||
|
ce, ok := vspec.Values[0].(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, ok := ce.Fun.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typ = id.Name
|
||||||
|
}
|
||||||
|
if vspec.Type != nil {
|
||||||
|
// "X T". We have a type. Remember it.
|
||||||
|
ident, ok := vspec.Type.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typ = ident.Name
|
||||||
|
}
|
||||||
|
if typ != f.typeName {
|
||||||
|
// This is not the type we're looking for.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We now have a list of names (from one line of source code) all being
|
||||||
|
// declared with the desired type.
|
||||||
|
// Grab their names and actual values and store them in f.values.
|
||||||
|
for _, name := range vspec.Names {
|
||||||
|
if name.Name == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This dance lets the type checker find the values for us. It's a
|
||||||
|
// bit tricky: look up the object declared by the name, find its
|
||||||
|
// types.Const, and extract its value.
|
||||||
|
obj, ok := f.pkg.defs[name]
|
||||||
|
if !ok {
|
||||||
|
log.Fatalf("no value for constant %s", name)
|
||||||
|
}
|
||||||
|
info := obj.Type().Underlying().(*types.Basic).Info()
|
||||||
|
if info&types.IsInteger == 0 {
|
||||||
|
log.Fatalf("can't handle non-integer constant type %s", typ)
|
||||||
|
}
|
||||||
|
value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
|
||||||
|
if value.Kind() != constant.Int {
|
||||||
|
log.Fatalf("can't happen: constant is not an integer %s", name)
|
||||||
|
}
|
||||||
|
i64, isInt := constant.Int64Val(value)
|
||||||
|
u64, isUint := constant.Uint64Val(value)
|
||||||
|
if !isInt && !isUint {
|
||||||
|
log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String())
|
||||||
|
}
|
||||||
|
if !isInt {
|
||||||
|
u64 = uint64(i64)
|
||||||
|
}
|
||||||
|
v := Value{
|
||||||
|
originalName: name.Name,
|
||||||
|
value: u64,
|
||||||
|
signed: info&types.IsUnsigned == 0,
|
||||||
|
str: value.String(),
|
||||||
|
}
|
||||||
|
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
|
||||||
|
v.name = strings.TrimSpace(c.Text())
|
||||||
|
} else {
|
||||||
|
v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
|
||||||
|
}
|
||||||
|
f.values = append(f.values, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
// usize returns the number of bits of the smallest unsigned integer
|
||||||
|
// type that will hold n. Used to create the smallest possible slice of
|
||||||
|
// integers to use as indexes into the concatenated strings.
|
||||||
|
func usize(n int) int {
|
||||||
|
switch {
|
||||||
|
case n < 1<<8:
|
||||||
|
return 8
|
||||||
|
case n < 1<<16:
|
||||||
|
return 16
|
||||||
|
default:
|
||||||
|
// 2^32 is enough constants for anyone.
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// declareIndexAndNameVars declares the index slices and concatenated names
|
||||||
|
// strings representing the runs of values.
|
||||||
|
func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) {
|
||||||
|
var indexes, names []string
|
||||||
|
for i, run := range runs {
|
||||||
|
index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i))
|
||||||
|
if len(run) != 1 {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
g.Printf("const (\n")
|
||||||
|
for _, name := range names {
|
||||||
|
g.Printf("\t%s\n", name)
|
||||||
|
}
|
||||||
|
g.Printf(")\n\n")
|
||||||
|
|
||||||
|
if len(indexes) > 0 {
|
||||||
|
g.Printf("var (")
|
||||||
|
for _, index := range indexes {
|
||||||
|
g.Printf("\t%s\n", index)
|
||||||
|
}
|
||||||
|
g.Printf(")\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// declareIndexAndNameVar is the single-run version of declareIndexAndNameVars
|
||||||
|
func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) {
|
||||||
|
index, name := g.createIndexAndNameDecl(run, typeName, "")
|
||||||
|
g.Printf("const %s\n", name)
|
||||||
|
g.Printf("var %s\n", index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var".
|
||||||
|
func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
indexes := make([]int, len(run))
|
||||||
|
for i := range run {
|
||||||
|
b.WriteString(run[i].name)
|
||||||
|
indexes[i] = b.Len()
|
||||||
|
}
|
||||||
|
nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String())
|
||||||
|
nameLen := b.Len()
|
||||||
|
b.Reset()
|
||||||
|
fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen))
|
||||||
|
for i, v := range indexes {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprintf(b, ", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "%d", v)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "}")
|
||||||
|
return b.String(), nameConst
|
||||||
|
}
|
||||||
|
|
||||||
|
// declareNameVars declares the concatenated names string representing all the values in the runs.
|
||||||
|
func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) {
|
||||||
|
g.Printf("const _%s_name%s = \"", typeName, suffix)
|
||||||
|
for _, run := range runs {
|
||||||
|
for i := range run {
|
||||||
|
g.Printf("%s", run[i].name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Printf("\"\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOneRun generates the variables and String method for a single run of contiguous values.
|
||||||
|
func (g *Generator) buildOneRun(runs [][]Value, typeName string) {
|
||||||
|
values := runs[0]
|
||||||
|
g.Printf("\n")
|
||||||
|
g.declareIndexAndNameVar(values, typeName)
|
||||||
|
// The generated code is simple enough to write as a Printf format.
|
||||||
|
lessThanZero := ""
|
||||||
|
if values[0].signed {
|
||||||
|
lessThanZero = "i < 0 || "
|
||||||
|
}
|
||||||
|
if values[0].value == 0 { // Signed or unsigned, 0 is still 0.
|
||||||
|
g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero)
|
||||||
|
} else {
|
||||||
|
g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments to format are:
|
||||||
|
// [1]: type name
|
||||||
|
// [2]: size of index element (8 for uint8 etc.)
|
||||||
|
// [3]: less than zero check (for signed types)
|
||||||
|
const stringOneRun = `func (i %[1]s) String() string {
|
||||||
|
if %[3]si >= %[1]s(len(_%[1]s_index)-1) {
|
||||||
|
return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// Arguments to format are:
|
||||||
|
// [1]: type name
|
||||||
|
// [2]: lowest defined value for type, as a string
|
||||||
|
// [3]: size of index element (8 for uint8 etc.)
|
||||||
|
// [4]: less than zero check (for signed types)
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
const stringOneRunWithOffset = `func (i %[1]s) String() string {
|
||||||
|
i -= %[2]s
|
||||||
|
if %[4]si >= %[1]s(len(_%[1]s_index)-1) {
|
||||||
|
return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")"
|
||||||
|
}
|
||||||
|
return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// buildMultipleRuns generates the variables and String method for multiple runs of contiguous values.
|
||||||
|
// For this pattern, a single Printf format won't do.
|
||||||
|
func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {
|
||||||
|
g.Printf("\n")
|
||||||
|
g.declareIndexAndNameVars(runs, typeName)
|
||||||
|
g.Printf("func (i %s) String() string {\n", typeName)
|
||||||
|
g.Printf("\tswitch {\n")
|
||||||
|
for i, values := range runs {
|
||||||
|
if len(values) == 1 {
|
||||||
|
g.Printf("\tcase i == %s:\n", &values[0])
|
||||||
|
g.Printf("\t\treturn _%s_name_%d\n", typeName, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if values[0].value == 0 && !values[0].signed {
|
||||||
|
// For an unsigned lower bound of 0, "0 <= i" would be redundant.
|
||||||
|
g.Printf("\tcase i <= %s:\n", &values[len(values)-1])
|
||||||
|
} else {
|
||||||
|
g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1])
|
||||||
|
}
|
||||||
|
if values[0].value != 0 {
|
||||||
|
g.Printf("\t\ti -= %s\n", &values[0])
|
||||||
|
}
|
||||||
|
g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n",
|
||||||
|
typeName, i, typeName, i, typeName, i)
|
||||||
|
}
|
||||||
|
g.Printf("\tdefault:\n")
|
||||||
|
g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName)
|
||||||
|
g.Printf("\t}\n")
|
||||||
|
g.Printf("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildMap handles the case where the space is so sparse a map is a reasonable fallback.
|
||||||
|
// It's a rare situation but has simple code.
|
||||||
|
func (g *Generator) buildMap(runs [][]Value, typeName string) {
|
||||||
|
g.Printf("\n")
|
||||||
|
g.declareNameVars(runs, typeName, "")
|
||||||
|
g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName)
|
||||||
|
n := 0
|
||||||
|
for _, values := range runs {
|
||||||
|
for _, value := range values {
|
||||||
|
g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name))
|
||||||
|
n += len(value.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Printf("}\n\n")
|
||||||
|
g.Printf(stringMap, typeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Argument to format is the type name.
|
||||||
|
const stringMap = `func (i %[1]s) String() string {
|
||||||
|
if str, ok := _%[1]s_map[i]; ok {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gcexportdata provides functions for locating, reading, and
|
||||||
|
// writing export data files containing type information produced by the
|
||||||
|
// gc compiler. This package supports go1.7 export data format and all
|
||||||
|
// later versions.
|
||||||
|
//
|
||||||
|
// Although it might seem convenient for this package to live alongside
|
||||||
|
// go/types in the standard library, this would cause version skew
|
||||||
|
// problems for developer tools that use it, since they must be able to
|
||||||
|
// consume the outputs of the gc compiler both before and after a Go
|
||||||
|
// update such as from Go 1.7 to Go 1.8. Because this package lives in
|
||||||
|
// golang.org/x/tools, sites can update their version of this repo some
|
||||||
|
// time before the Go 1.8 release and rebuild and redeploy their
|
||||||
|
// developer tools, which will then be able to consume both Go 1.7 and
|
||||||
|
// Go 1.8 export data files, so they will work before and after the
|
||||||
|
// Go update. (See discussion at https://golang.org/issue/15651.)
|
||||||
|
//
|
||||||
|
package gcexportdata // import "golang.org/x/tools/go/gcexportdata"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/internal/gcimporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find returns the name of an object (.o) or archive (.a) file
|
||||||
|
// containing type information for the specified import path,
|
||||||
|
// using the workspace layout conventions of go/build.
|
||||||
|
// If no file was found, an empty filename is returned.
|
||||||
|
//
|
||||||
|
// A relative srcDir is interpreted relative to the current working directory.
|
||||||
|
//
|
||||||
|
// Find also returns the package's resolved (canonical) import path,
|
||||||
|
// reflecting the effects of srcDir and vendoring on importPath.
|
||||||
|
func Find(importPath, srcDir string) (filename, path string) {
|
||||||
|
return gcimporter.FindPkg(importPath, srcDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a reader for the export data section of an object
|
||||||
|
// (.o) or archive (.a) file read from r. The new reader may provide
|
||||||
|
// additional trailing data beyond the end of the export data.
|
||||||
|
func NewReader(r io.Reader) (io.Reader, error) {
|
||||||
|
buf := bufio.NewReader(r)
|
||||||
|
_, err := gcimporter.FindExportData(buf)
|
||||||
|
// If we ever switch to a zip-like archive format with the ToC
|
||||||
|
// at the end, we can return the correct portion of export data,
|
||||||
|
// but for now we must return the entire rest of the file.
|
||||||
|
return buf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads export data from in, decodes it, and returns type
|
||||||
|
// information for the package.
|
||||||
|
// The package name is specified by path.
|
||||||
|
// File position information is added to fset.
|
||||||
|
//
|
||||||
|
// Read may inspect and add to the imports map to ensure that references
|
||||||
|
// within the export data to other packages are consistent. The caller
|
||||||
|
// must ensure that imports[path] does not exist, or exists but is
|
||||||
|
// incomplete (see types.Package.Complete), and Read inserts the
|
||||||
|
// resulting package into this map entry.
|
||||||
|
//
|
||||||
|
// On return, the state of the reader is undefined.
|
||||||
|
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||||
|
data, err := ioutil.ReadAll(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(data, []byte("!<arch>")) {
|
||||||
|
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The App Engine Go runtime v1.6 uses the old export data format.
|
||||||
|
// TODO(adonovan): delete once v1.7 has been around for a while.
|
||||||
|
if bytes.HasPrefix(data, []byte("package ")) {
|
||||||
|
return gcimporter.ImportData(imports, path, path, bytes.NewReader(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The indexed export format starts with an 'i'; the older
|
||||||
|
// binary export format starts with a 'c', 'd', or 'v'
|
||||||
|
// (from "version"). Select appropriate importer.
|
||||||
|
if len(data) > 0 && data[0] == 'i' {
|
||||||
|
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||||
|
return pkg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pkg, err := gcimporter.BImportData(fset, imports, data, path)
|
||||||
|
return pkg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes encoded type information for the specified package to out.
|
||||||
|
// The FileSet provides file position information for named objects.
|
||||||
|
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
|
||||||
|
b, err := gcimporter.IExportData(fset, pkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = out.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gcexportdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewImporter returns a new instance of the types.Importer interface
|
||||||
|
// that reads type information from export data files written by gc.
|
||||||
|
// The Importer also satisfies types.ImporterFrom.
|
||||||
|
//
|
||||||
|
// Export data files are located using "go build" workspace conventions
|
||||||
|
// and the build.Default context.
|
||||||
|
//
|
||||||
|
// Use this importer instead of go/importer.For("gc", ...) to avoid the
|
||||||
|
// version-skew problems described in the documentation of this package,
|
||||||
|
// or to control the FileSet or access the imports map populated during
|
||||||
|
// package loading.
|
||||||
|
//
|
||||||
|
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
|
||||||
|
return importer{fset, imports}
|
||||||
|
}
|
||||||
|
|
||||||
|
type importer struct {
|
||||||
|
fset *token.FileSet
|
||||||
|
imports map[string]*types.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
func (imp importer) Import(importPath string) (*types.Package, error) {
|
||||||
|
return imp.ImportFrom(importPath, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
|
||||||
|
filename, path := Find(importPath, srcDir)
|
||||||
|
if filename == "" {
|
||||||
|
if importPath == "unsafe" {
|
||||||
|
// Even for unsafe, call Find first in case
|
||||||
|
// the package was vendored.
|
||||||
|
return types.Unsafe, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("can't find import: %s", importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
|
||||||
|
return pkg, nil // cache hit
|
||||||
|
}
|
||||||
|
|
||||||
|
// open file
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
// add file name to error
|
||||||
|
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
r, err := NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Read(r, imp.fset, imp.imports, path)
|
||||||
|
}
|
|
@ -0,0 +1,852 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Binary package export.
|
||||||
|
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go;
|
||||||
|
// see that file for specification of the format.
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If debugFormat is set, each integer and string value is preceded by a marker
|
||||||
|
// and position information in the encoding. This mechanism permits an importer
|
||||||
|
// to recognize immediately when it is out of sync. The importer recognizes this
|
||||||
|
// mode automatically (i.e., it can import export data produced with debugging
|
||||||
|
// support even if debugFormat is not set at the time of import). This mode will
|
||||||
|
// lead to massively larger export data (by a factor of 2 to 3) and should only
|
||||||
|
// be enabled during development and debugging.
|
||||||
|
//
|
||||||
|
// NOTE: This flag is the first flag to enable if importing dies because of
|
||||||
|
// (suspected) format errors, and whenever a change is made to the format.
|
||||||
|
const debugFormat = false // default: false
|
||||||
|
|
||||||
|
// If trace is set, debugging output is printed to std out.
|
||||||
|
const trace = false // default: false
|
||||||
|
|
||||||
|
// Current export format version. Increase with each format change.
|
||||||
|
// Note: The latest binary (non-indexed) export format is at version 6.
|
||||||
|
// This exporter is still at level 4, but it doesn't matter since
|
||||||
|
// the binary importer can handle older versions just fine.
|
||||||
|
// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE
|
||||||
|
// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE
|
||||||
|
// 4: type name objects support type aliases, uses aliasTag
|
||||||
|
// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used)
|
||||||
|
// 2: removed unused bool in ODCL export (compiler only)
|
||||||
|
// 1: header format change (more regular), export package for _ struct fields
|
||||||
|
// 0: Go1.7 encoding
|
||||||
|
const exportVersion = 4
|
||||||
|
|
||||||
|
// trackAllTypes enables cycle tracking for all types, not just named
|
||||||
|
// types. The existing compiler invariants assume that unnamed types
|
||||||
|
// that are not completely set up are not used, or else there are spurious
|
||||||
|
// errors.
|
||||||
|
// If disabled, only named types are tracked, possibly leading to slightly
|
||||||
|
// less efficient encoding in rare cases. It also prevents the export of
|
||||||
|
// some corner-case type declarations (but those are not handled correctly
|
||||||
|
// with with the textual export format either).
|
||||||
|
// TODO(gri) enable and remove once issues caused by it are fixed
|
||||||
|
const trackAllTypes = false
|
||||||
|
|
||||||
|
type exporter struct {
|
||||||
|
fset *token.FileSet
|
||||||
|
out bytes.Buffer
|
||||||
|
|
||||||
|
// object -> index maps, indexed in order of serialization
|
||||||
|
strIndex map[string]int
|
||||||
|
pkgIndex map[*types.Package]int
|
||||||
|
typIndex map[types.Type]int
|
||||||
|
|
||||||
|
// position encoding
|
||||||
|
posInfoFormat bool
|
||||||
|
prevFile string
|
||||||
|
prevLine int
|
||||||
|
|
||||||
|
// debugging support
|
||||||
|
written int // bytes written
|
||||||
|
indent int // for trace
|
||||||
|
}
|
||||||
|
|
||||||
|
// internalError represents an error generated inside this package.
|
||||||
|
type internalError string
|
||||||
|
|
||||||
|
func (e internalError) Error() string { return "gcimporter: " + string(e) }
|
||||||
|
|
||||||
|
func internalErrorf(format string, args ...interface{}) error {
|
||||||
|
return internalError(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BExportData returns binary export data for pkg.
|
||||||
|
// If no file set is provided, position info will be missing.
|
||||||
|
func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
if ierr, ok := e.(internalError); ok {
|
||||||
|
err = ierr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Not an internal error; panic again.
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p := exporter{
|
||||||
|
fset: fset,
|
||||||
|
strIndex: map[string]int{"": 0}, // empty string is mapped to 0
|
||||||
|
pkgIndex: make(map[*types.Package]int),
|
||||||
|
typIndex: make(map[types.Type]int),
|
||||||
|
posInfoFormat: true, // TODO(gri) might become a flag, eventually
|
||||||
|
}
|
||||||
|
|
||||||
|
// write version info
|
||||||
|
// The version string must start with "version %d" where %d is the version
|
||||||
|
// number. Additional debugging information may follow after a blank; that
|
||||||
|
// text is ignored by the importer.
|
||||||
|
p.rawStringln(fmt.Sprintf("version %d", exportVersion))
|
||||||
|
var debug string
|
||||||
|
if debugFormat {
|
||||||
|
debug = "debug"
|
||||||
|
}
|
||||||
|
p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly
|
||||||
|
p.bool(trackAllTypes)
|
||||||
|
p.bool(p.posInfoFormat)
|
||||||
|
|
||||||
|
// --- generic export data ---
|
||||||
|
|
||||||
|
// populate type map with predeclared "known" types
|
||||||
|
for index, typ := range predeclared() {
|
||||||
|
p.typIndex[typ] = index
|
||||||
|
}
|
||||||
|
if len(p.typIndex) != len(predeclared()) {
|
||||||
|
return nil, internalError("duplicate entries in type map?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// write package data
|
||||||
|
p.pkg(pkg, true)
|
||||||
|
if trace {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// write objects
|
||||||
|
objcount := 0
|
||||||
|
scope := pkg.Scope()
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
if !ast.IsExported(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if trace {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
p.obj(scope.Lookup(name))
|
||||||
|
objcount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// indicate end of list
|
||||||
|
if trace {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
p.tag(endTag)
|
||||||
|
|
||||||
|
// for self-verification only (redundant)
|
||||||
|
p.int(objcount)
|
||||||
|
|
||||||
|
if trace {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- end of export data ---
|
||||||
|
|
||||||
|
return p.out.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) pkg(pkg *types.Package, emptypath bool) {
|
||||||
|
if pkg == nil {
|
||||||
|
panic(internalError("unexpected nil pkg"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we saw the package before, write its index (>= 0)
|
||||||
|
if i, ok := p.pkgIndex[pkg]; ok {
|
||||||
|
p.index('P', i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, remember the package, write the package tag (< 0) and package data
|
||||||
|
if trace {
|
||||||
|
p.tracef("P%d = { ", len(p.pkgIndex))
|
||||||
|
defer p.tracef("} ")
|
||||||
|
}
|
||||||
|
p.pkgIndex[pkg] = len(p.pkgIndex)
|
||||||
|
|
||||||
|
p.tag(packageTag)
|
||||||
|
p.string(pkg.Name())
|
||||||
|
if emptypath {
|
||||||
|
p.string("")
|
||||||
|
} else {
|
||||||
|
p.string(pkg.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) obj(obj types.Object) {
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case *types.Const:
|
||||||
|
p.tag(constTag)
|
||||||
|
p.pos(obj)
|
||||||
|
p.qualifiedName(obj)
|
||||||
|
p.typ(obj.Type())
|
||||||
|
p.value(obj.Val())
|
||||||
|
|
||||||
|
case *types.TypeName:
|
||||||
|
if obj.IsAlias() {
|
||||||
|
p.tag(aliasTag)
|
||||||
|
p.pos(obj)
|
||||||
|
p.qualifiedName(obj)
|
||||||
|
} else {
|
||||||
|
p.tag(typeTag)
|
||||||
|
}
|
||||||
|
p.typ(obj.Type())
|
||||||
|
|
||||||
|
case *types.Var:
|
||||||
|
p.tag(varTag)
|
||||||
|
p.pos(obj)
|
||||||
|
p.qualifiedName(obj)
|
||||||
|
p.typ(obj.Type())
|
||||||
|
|
||||||
|
case *types.Func:
|
||||||
|
p.tag(funcTag)
|
||||||
|
p.pos(obj)
|
||||||
|
p.qualifiedName(obj)
|
||||||
|
sig := obj.Type().(*types.Signature)
|
||||||
|
p.paramList(sig.Params(), sig.Variadic())
|
||||||
|
p.paramList(sig.Results(), false)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected object %v (%T)", obj, obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) pos(obj types.Object) {
|
||||||
|
if !p.posInfoFormat {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, line := p.fileLine(obj)
|
||||||
|
if file == p.prevFile {
|
||||||
|
// common case: write line delta
|
||||||
|
// delta == 0 means different file or no line change
|
||||||
|
delta := line - p.prevLine
|
||||||
|
p.int(delta)
|
||||||
|
if delta == 0 {
|
||||||
|
p.int(-1) // -1 means no file change
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// different file
|
||||||
|
p.int(0)
|
||||||
|
// Encode filename as length of common prefix with previous
|
||||||
|
// filename, followed by (possibly empty) suffix. Filenames
|
||||||
|
// frequently share path prefixes, so this can save a lot
|
||||||
|
// of space and make export data size less dependent on file
|
||||||
|
// path length. The suffix is unlikely to be empty because
|
||||||
|
// file names tend to end in ".go".
|
||||||
|
n := commonPrefixLen(p.prevFile, file)
|
||||||
|
p.int(n) // n >= 0
|
||||||
|
p.string(file[n:]) // write suffix only
|
||||||
|
p.prevFile = file
|
||||||
|
p.int(line)
|
||||||
|
}
|
||||||
|
p.prevLine = line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) fileLine(obj types.Object) (file string, line int) {
|
||||||
|
if p.fset != nil {
|
||||||
|
pos := p.fset.Position(obj.Pos())
|
||||||
|
file = pos.Filename
|
||||||
|
line = pos.Line
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonPrefixLen(a, b string) int {
|
||||||
|
if len(a) > len(b) {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
// len(a) <= len(b)
|
||||||
|
i := 0
|
||||||
|
for i < len(a) && a[i] == b[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) qualifiedName(obj types.Object) {
|
||||||
|
p.string(obj.Name())
|
||||||
|
p.pkg(obj.Pkg(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) typ(t types.Type) {
|
||||||
|
if t == nil {
|
||||||
|
panic(internalError("nil type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possible optimization: Anonymous pointer types *T where
|
||||||
|
// T is a named type are common. We could canonicalize all
|
||||||
|
// such types *T to a single type PT = *T. This would lead
|
||||||
|
// to at most one *T entry in typIndex, and all future *T's
|
||||||
|
// would be encoded as the respective index directly. Would
|
||||||
|
// save 1 byte (pointerTag) per *T and reduce the typIndex
|
||||||
|
// size (at the cost of a canonicalization map). We can do
|
||||||
|
// this later, without encoding format change.
|
||||||
|
|
||||||
|
// if we saw the type before, write its index (>= 0)
|
||||||
|
if i, ok := p.typIndex[t]; ok {
|
||||||
|
p.index('T', i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, remember the type, write the type tag (< 0) and type data
|
||||||
|
if trackAllTypes {
|
||||||
|
if trace {
|
||||||
|
p.tracef("T%d = {>\n", len(p.typIndex))
|
||||||
|
defer p.tracef("<\n} ")
|
||||||
|
}
|
||||||
|
p.typIndex[t] = len(p.typIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.Named:
|
||||||
|
if !trackAllTypes {
|
||||||
|
// if we don't track all types, track named types now
|
||||||
|
p.typIndex[t] = len(p.typIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.tag(namedTag)
|
||||||
|
p.pos(t.Obj())
|
||||||
|
p.qualifiedName(t.Obj())
|
||||||
|
p.typ(t.Underlying())
|
||||||
|
if !types.IsInterface(t) {
|
||||||
|
p.assocMethods(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.Array:
|
||||||
|
p.tag(arrayTag)
|
||||||
|
p.int64(t.Len())
|
||||||
|
p.typ(t.Elem())
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
p.tag(sliceTag)
|
||||||
|
p.typ(t.Elem())
|
||||||
|
|
||||||
|
case *dddSlice:
|
||||||
|
p.tag(dddTag)
|
||||||
|
p.typ(t.elem)
|
||||||
|
|
||||||
|
case *types.Struct:
|
||||||
|
p.tag(structTag)
|
||||||
|
p.fieldList(t)
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
p.tag(pointerTag)
|
||||||
|
p.typ(t.Elem())
|
||||||
|
|
||||||
|
case *types.Signature:
|
||||||
|
p.tag(signatureTag)
|
||||||
|
p.paramList(t.Params(), t.Variadic())
|
||||||
|
p.paramList(t.Results(), false)
|
||||||
|
|
||||||
|
case *types.Interface:
|
||||||
|
p.tag(interfaceTag)
|
||||||
|
p.iface(t)
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
p.tag(mapTag)
|
||||||
|
p.typ(t.Key())
|
||||||
|
p.typ(t.Elem())
|
||||||
|
|
||||||
|
case *types.Chan:
|
||||||
|
p.tag(chanTag)
|
||||||
|
p.int(int(3 - t.Dir())) // hack
|
||||||
|
p.typ(t.Elem())
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected type %T: %s", t, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) assocMethods(named *types.Named) {
|
||||||
|
// Sort methods (for determinism).
|
||||||
|
var methods []*types.Func
|
||||||
|
for i := 0; i < named.NumMethods(); i++ {
|
||||||
|
methods = append(methods, named.Method(i))
|
||||||
|
}
|
||||||
|
sort.Sort(methodsByName(methods))
|
||||||
|
|
||||||
|
p.int(len(methods))
|
||||||
|
|
||||||
|
if trace && methods != nil {
|
||||||
|
p.tracef("associated methods {>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, m := range methods {
|
||||||
|
if trace && i > 0 {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pos(m)
|
||||||
|
name := m.Name()
|
||||||
|
p.string(name)
|
||||||
|
if !exported(name) {
|
||||||
|
p.pkg(m.Pkg(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := m.Type().(*types.Signature)
|
||||||
|
p.paramList(types.NewTuple(sig.Recv()), false)
|
||||||
|
p.paramList(sig.Params(), sig.Variadic())
|
||||||
|
p.paramList(sig.Results(), false)
|
||||||
|
p.int(0) // dummy value for go:nointerface pragma - ignored by importer
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace && methods != nil {
|
||||||
|
p.tracef("<\n} ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodsByName []*types.Func
|
||||||
|
|
||||||
|
func (x methodsByName) Len() int { return len(x) }
|
||||||
|
func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
|
||||||
|
|
||||||
|
func (p *exporter) fieldList(t *types.Struct) {
|
||||||
|
if trace && t.NumFields() > 0 {
|
||||||
|
p.tracef("fields {>\n")
|
||||||
|
defer p.tracef("<\n} ")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.int(t.NumFields())
|
||||||
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
|
if trace && i > 0 {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
p.field(t.Field(i))
|
||||||
|
p.string(t.Tag(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) field(f *types.Var) {
|
||||||
|
if !f.IsField() {
|
||||||
|
panic(internalError("field expected"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pos(f)
|
||||||
|
p.fieldName(f)
|
||||||
|
p.typ(f.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) iface(t *types.Interface) {
|
||||||
|
// TODO(gri): enable importer to load embedded interfaces,
|
||||||
|
// then emit Embeddeds and ExplicitMethods separately here.
|
||||||
|
p.int(0)
|
||||||
|
|
||||||
|
n := t.NumMethods()
|
||||||
|
if trace && n > 0 {
|
||||||
|
p.tracef("methods {>\n")
|
||||||
|
defer p.tracef("<\n} ")
|
||||||
|
}
|
||||||
|
p.int(n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if trace && i > 0 {
|
||||||
|
p.tracef("\n")
|
||||||
|
}
|
||||||
|
p.method(t.Method(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) method(m *types.Func) {
|
||||||
|
sig := m.Type().(*types.Signature)
|
||||||
|
if sig.Recv() == nil {
|
||||||
|
panic(internalError("method expected"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pos(m)
|
||||||
|
p.string(m.Name())
|
||||||
|
if m.Name() != "_" && !ast.IsExported(m.Name()) {
|
||||||
|
p.pkg(m.Pkg(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface method; no need to encode receiver.
|
||||||
|
p.paramList(sig.Params(), sig.Variadic())
|
||||||
|
p.paramList(sig.Results(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) fieldName(f *types.Var) {
|
||||||
|
name := f.Name()
|
||||||
|
|
||||||
|
if f.Anonymous() {
|
||||||
|
// anonymous field - we distinguish between 3 cases:
|
||||||
|
// 1) field name matches base type name and is exported
|
||||||
|
// 2) field name matches base type name and is not exported
|
||||||
|
// 3) field name doesn't match base type name (alias name)
|
||||||
|
bname := basetypeName(f.Type())
|
||||||
|
if name == bname {
|
||||||
|
if ast.IsExported(name) {
|
||||||
|
name = "" // 1) we don't need to know the field name or package
|
||||||
|
} else {
|
||||||
|
name = "?" // 2) use unexported name "?" to force package export
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 3) indicate alias and export name as is
|
||||||
|
// (this requires an extra "@" but this is a rare case)
|
||||||
|
p.string("@")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.string(name)
|
||||||
|
if name != "" && !ast.IsExported(name) {
|
||||||
|
p.pkg(f.Pkg(), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func basetypeName(typ types.Type) string {
|
||||||
|
switch typ := deref(typ).(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
return typ.Name()
|
||||||
|
case *types.Named:
|
||||||
|
return typ.Obj().Name()
|
||||||
|
default:
|
||||||
|
return "" // unnamed type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) paramList(params *types.Tuple, variadic bool) {
|
||||||
|
// use negative length to indicate unnamed parameters
|
||||||
|
// (look at the first parameter only since either all
|
||||||
|
// names are present or all are absent)
|
||||||
|
n := params.Len()
|
||||||
|
if n > 0 && params.At(0).Name() == "" {
|
||||||
|
n = -n
|
||||||
|
}
|
||||||
|
p.int(n)
|
||||||
|
for i := 0; i < params.Len(); i++ {
|
||||||
|
q := params.At(i)
|
||||||
|
t := q.Type()
|
||||||
|
if variadic && i == params.Len()-1 {
|
||||||
|
t = &dddSlice{t.(*types.Slice).Elem()}
|
||||||
|
}
|
||||||
|
p.typ(t)
|
||||||
|
if n > 0 {
|
||||||
|
name := q.Name()
|
||||||
|
p.string(name)
|
||||||
|
if name != "_" {
|
||||||
|
p.pkg(q.Pkg(), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.string("") // no compiler-specific info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) value(x constant.Value) {
|
||||||
|
if trace {
|
||||||
|
p.tracef("= ")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x.Kind() {
|
||||||
|
case constant.Bool:
|
||||||
|
tag := falseTag
|
||||||
|
if constant.BoolVal(x) {
|
||||||
|
tag = trueTag
|
||||||
|
}
|
||||||
|
p.tag(tag)
|
||||||
|
|
||||||
|
case constant.Int:
|
||||||
|
if v, exact := constant.Int64Val(x); exact {
|
||||||
|
// common case: x fits into an int64 - use compact encoding
|
||||||
|
p.tag(int64Tag)
|
||||||
|
p.int64(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// uncommon case: large x - use float encoding
|
||||||
|
// (powers of 2 will be encoded efficiently with exponent)
|
||||||
|
p.tag(floatTag)
|
||||||
|
p.float(constant.ToFloat(x))
|
||||||
|
|
||||||
|
case constant.Float:
|
||||||
|
p.tag(floatTag)
|
||||||
|
p.float(x)
|
||||||
|
|
||||||
|
case constant.Complex:
|
||||||
|
p.tag(complexTag)
|
||||||
|
p.float(constant.Real(x))
|
||||||
|
p.float(constant.Imag(x))
|
||||||
|
|
||||||
|
case constant.String:
|
||||||
|
p.tag(stringTag)
|
||||||
|
p.string(constant.StringVal(x))
|
||||||
|
|
||||||
|
case constant.Unknown:
|
||||||
|
// package contains type errors
|
||||||
|
p.tag(unknownTag)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected value %v (%T)", x, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) float(x constant.Value) {
|
||||||
|
if x.Kind() != constant.Float {
|
||||||
|
panic(internalErrorf("unexpected constant %v, want float", x))
|
||||||
|
}
|
||||||
|
// extract sign (there is no -0)
|
||||||
|
sign := constant.Sign(x)
|
||||||
|
if sign == 0 {
|
||||||
|
// x == 0
|
||||||
|
p.int(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// x != 0
|
||||||
|
|
||||||
|
var f big.Float
|
||||||
|
if v, exact := constant.Float64Val(x); exact {
|
||||||
|
// float64
|
||||||
|
f.SetFloat64(v)
|
||||||
|
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||||
|
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||||
|
r := valueToRat(num)
|
||||||
|
f.SetRat(r.Quo(r, valueToRat(denom)))
|
||||||
|
} else {
|
||||||
|
// Value too large to represent as a fraction => inaccessible.
|
||||||
|
// TODO(gri): add big.Float accessor to constant.Value.
|
||||||
|
f.SetFloat64(math.MaxFloat64) // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract exponent such that 0.5 <= m < 1.0
|
||||||
|
var m big.Float
|
||||||
|
exp := f.MantExp(&m)
|
||||||
|
|
||||||
|
// extract mantissa as *big.Int
|
||||||
|
// - set exponent large enough so mant satisfies mant.IsInt()
|
||||||
|
// - get *big.Int from mant
|
||||||
|
m.SetMantExp(&m, int(m.MinPrec()))
|
||||||
|
mant, acc := m.Int(nil)
|
||||||
|
if acc != big.Exact {
|
||||||
|
panic(internalError("internal error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.int(sign)
|
||||||
|
p.int(exp)
|
||||||
|
p.string(string(mant.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToRat(x constant.Value) *big.Rat {
|
||||||
|
// Convert little-endian to big-endian.
|
||||||
|
// I can't believe this is necessary.
|
||||||
|
bytes := constant.Bytes(x)
|
||||||
|
for i := 0; i < len(bytes)/2; i++ {
|
||||||
|
bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
|
||||||
|
}
|
||||||
|
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) bool(b bool) bool {
|
||||||
|
if trace {
|
||||||
|
p.tracef("[")
|
||||||
|
defer p.tracef("= %v] ", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
x := 0
|
||||||
|
if b {
|
||||||
|
x = 1
|
||||||
|
}
|
||||||
|
p.int(x)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Low-level encoders
|
||||||
|
|
||||||
|
func (p *exporter) index(marker byte, index int) {
|
||||||
|
if index < 0 {
|
||||||
|
panic(internalError("invalid index < 0"))
|
||||||
|
}
|
||||||
|
if debugFormat {
|
||||||
|
p.marker('t')
|
||||||
|
}
|
||||||
|
if trace {
|
||||||
|
p.tracef("%c%d ", marker, index)
|
||||||
|
}
|
||||||
|
p.rawInt64(int64(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) tag(tag int) {
|
||||||
|
if tag >= 0 {
|
||||||
|
panic(internalError("invalid tag >= 0"))
|
||||||
|
}
|
||||||
|
if debugFormat {
|
||||||
|
p.marker('t')
|
||||||
|
}
|
||||||
|
if trace {
|
||||||
|
p.tracef("%s ", tagString[-tag])
|
||||||
|
}
|
||||||
|
p.rawInt64(int64(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) int(x int) {
|
||||||
|
p.int64(int64(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) int64(x int64) {
|
||||||
|
if debugFormat {
|
||||||
|
p.marker('i')
|
||||||
|
}
|
||||||
|
if trace {
|
||||||
|
p.tracef("%d ", x)
|
||||||
|
}
|
||||||
|
p.rawInt64(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *exporter) string(s string) {
|
||||||
|
if debugFormat {
|
||||||
|
p.marker('s')
|
||||||
|
}
|
||||||
|
if trace {
|
||||||
|
p.tracef("%q ", s)
|
||||||
|
}
|
||||||
|
// if we saw the string before, write its index (>= 0)
|
||||||
|
// (the empty string is mapped to 0)
|
||||||
|
if i, ok := p.strIndex[s]; ok {
|
||||||
|
p.rawInt64(int64(i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// otherwise, remember string and write its negative length and bytes
|
||||||
|
p.strIndex[s] = len(p.strIndex)
|
||||||
|
p.rawInt64(-int64(len(s)))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
p.rawByte(s[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marker emits a marker byte and position information which makes
|
||||||
|
// it easy for a reader to detect if it is "out of sync". Used for
|
||||||
|
// debugFormat format only.
|
||||||
|
func (p *exporter) marker(m byte) {
|
||||||
|
p.rawByte(m)
|
||||||
|
// Enable this for help tracking down the location
|
||||||
|
// of an incorrect marker when running in debugFormat.
|
||||||
|
if false && trace {
|
||||||
|
p.tracef("#%d ", p.written)
|
||||||
|
}
|
||||||
|
p.rawInt64(int64(p.written))
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawInt64 should only be used by low-level encoders.
|
||||||
|
func (p *exporter) rawInt64(x int64) {
|
||||||
|
var tmp [binary.MaxVarintLen64]byte
|
||||||
|
n := binary.PutVarint(tmp[:], x)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
p.rawByte(tmp[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawStringln should only be used to emit the initial version string.
|
||||||
|
func (p *exporter) rawStringln(s string) {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
p.rawByte(s[i])
|
||||||
|
}
|
||||||
|
p.rawByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawByte is the bottleneck interface to write to p.out.
|
||||||
|
// rawByte escapes b as follows (any encoding does that
|
||||||
|
// hides '$'):
|
||||||
|
//
|
||||||
|
// '$' => '|' 'S'
|
||||||
|
// '|' => '|' '|'
|
||||||
|
//
|
||||||
|
// Necessary so other tools can find the end of the
|
||||||
|
// export data by searching for "$$".
|
||||||
|
// rawByte should only be used by low-level encoders.
|
||||||
|
func (p *exporter) rawByte(b byte) {
|
||||||
|
switch b {
|
||||||
|
case '$':
|
||||||
|
// write '$' as '|' 'S'
|
||||||
|
b = 'S'
|
||||||
|
fallthrough
|
||||||
|
case '|':
|
||||||
|
// write '|' as '|' '|'
|
||||||
|
p.out.WriteByte('|')
|
||||||
|
p.written++
|
||||||
|
}
|
||||||
|
p.out.WriteByte(b)
|
||||||
|
p.written++
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracef is like fmt.Printf but it rewrites the format string
|
||||||
|
// to take care of indentation.
|
||||||
|
func (p *exporter) tracef(format string, args ...interface{}) {
|
||||||
|
if strings.ContainsAny(format, "<>\n") {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 0; i < len(format); i++ {
|
||||||
|
// no need to deal with runes
|
||||||
|
ch := format[i]
|
||||||
|
switch ch {
|
||||||
|
case '>':
|
||||||
|
p.indent++
|
||||||
|
continue
|
||||||
|
case '<':
|
||||||
|
p.indent--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.WriteByte(ch)
|
||||||
|
if ch == '\n' {
|
||||||
|
for j := p.indent; j > 0; j-- {
|
||||||
|
buf.WriteString(". ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format = buf.String()
|
||||||
|
}
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugging support.
|
||||||
|
// (tagString is only used when tracing is enabled)
|
||||||
|
var tagString = [...]string{
|
||||||
|
// Packages
|
||||||
|
-packageTag: "package",
|
||||||
|
|
||||||
|
// Types
|
||||||
|
-namedTag: "named type",
|
||||||
|
-arrayTag: "array",
|
||||||
|
-sliceTag: "slice",
|
||||||
|
-dddTag: "ddd",
|
||||||
|
-structTag: "struct",
|
||||||
|
-pointerTag: "pointer",
|
||||||
|
-signatureTag: "signature",
|
||||||
|
-interfaceTag: "interface",
|
||||||
|
-mapTag: "map",
|
||||||
|
-chanTag: "chan",
|
||||||
|
|
||||||
|
// Values
|
||||||
|
-falseTag: "false",
|
||||||
|
-trueTag: "true",
|
||||||
|
-int64Tag: "int64",
|
||||||
|
-floatTag: "float",
|
||||||
|
-fractionTag: "fraction",
|
||||||
|
-complexTag: "complex",
|
||||||
|
-stringTag: "string",
|
||||||
|
-unknownTag: "unknown",
|
||||||
|
|
||||||
|
// Type aliases
|
||||||
|
-aliasTag: "alias",
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go.
|
||||||
|
|
||||||
|
// This file implements FindExportData.
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
|
||||||
|
// See $GOROOT/include/ar.h.
|
||||||
|
hdr := make([]byte, 16+12+6+6+8+10+2)
|
||||||
|
_, err = io.ReadFull(r, hdr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// leave for debugging
|
||||||
|
if false {
|
||||||
|
fmt.Printf("header: %s", hdr)
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
|
||||||
|
size, err = strconv.Atoi(s)
|
||||||
|
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
|
||||||
|
err = fmt.Errorf("invalid archive header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = strings.TrimSpace(string(hdr[:16]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindExportData positions the reader r at the beginning of the
|
||||||
|
// export data section of an underlying GC-created object/archive
|
||||||
|
// file by reading from it. The reader must be positioned at the
|
||||||
|
// start of the file before calling this function. The hdr result
|
||||||
|
// is the string before the export data, either "$$" or "$$B".
|
||||||
|
//
|
||||||
|
func FindExportData(r *bufio.Reader) (hdr string, err error) {
|
||||||
|
// Read first line to make sure this is an object file.
|
||||||
|
line, err := r.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("can't find export data (%v)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(line) == "!<arch>\n" {
|
||||||
|
// Archive file. Scan to __.PKGDEF.
|
||||||
|
var name string
|
||||||
|
if name, _, err = readGopackHeader(r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First entry should be __.PKGDEF.
|
||||||
|
if name != "__.PKGDEF" {
|
||||||
|
err = fmt.Errorf("go archive is missing __.PKGDEF")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read first line of __.PKGDEF data, so that line
|
||||||
|
// is once again the first line of the input.
|
||||||
|
if line, err = r.ReadSlice('\n'); err != nil {
|
||||||
|
err = fmt.Errorf("can't find export data (%v)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now at __.PKGDEF in archive or still at beginning of file.
|
||||||
|
// Either way, line should begin with "go object ".
|
||||||
|
if !strings.HasPrefix(string(line), "go object ") {
|
||||||
|
err = fmt.Errorf("not a Go object file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over object header to export data.
|
||||||
|
// Begins after first line starting with $$.
|
||||||
|
for line[0] != '$' {
|
||||||
|
if line, err = r.ReadSlice('\n'); err != nil {
|
||||||
|
err = fmt.Errorf("can't find export data (%v)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hdr = string(line)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,739 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Indexed binary package export.
|
||||||
|
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go;
|
||||||
|
// see that file for specification of the format.
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current indexed export format version. Increase with each format change.
|
||||||
|
// 0: Go1.11 encoding
|
||||||
|
const iexportVersion = 0
|
||||||
|
|
||||||
|
// IExportData returns the binary export data for pkg.
|
||||||
|
//
|
||||||
|
// If no file set is provided, position info will be missing.
|
||||||
|
// The package path of the top-level package will not be recorded,
|
||||||
|
// so that calls to IImportData can override with a provided package path.
|
||||||
|
func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
if ierr, ok := e.(internalError); ok {
|
||||||
|
err = ierr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Not an internal error; panic again.
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p := iexporter{
|
||||||
|
out: bytes.NewBuffer(nil),
|
||||||
|
fset: fset,
|
||||||
|
allPkgs: map[*types.Package]bool{},
|
||||||
|
stringIndex: map[string]uint64{},
|
||||||
|
declIndex: map[types.Object]uint64{},
|
||||||
|
typIndex: map[types.Type]uint64{},
|
||||||
|
localpkg: pkg,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, pt := range predeclared() {
|
||||||
|
p.typIndex[pt] = uint64(i)
|
||||||
|
}
|
||||||
|
if len(p.typIndex) > predeclReserved {
|
||||||
|
panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize work queue with exported declarations.
|
||||||
|
scope := pkg.Scope()
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
if ast.IsExported(name) {
|
||||||
|
p.pushDecl(scope.Lookup(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop until no more work.
|
||||||
|
for !p.declTodo.empty() {
|
||||||
|
p.doDecl(p.declTodo.popHead())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append indices to data0 section.
|
||||||
|
dataLen := uint64(p.data0.Len())
|
||||||
|
w := p.newWriter()
|
||||||
|
w.writeIndex(p.declIndex)
|
||||||
|
w.flush()
|
||||||
|
|
||||||
|
// Assemble header.
|
||||||
|
var hdr intWriter
|
||||||
|
hdr.WriteByte('i')
|
||||||
|
hdr.uint64(iexportVersion)
|
||||||
|
hdr.uint64(uint64(p.strings.Len()))
|
||||||
|
hdr.uint64(dataLen)
|
||||||
|
|
||||||
|
// Flush output.
|
||||||
|
io.Copy(p.out, &hdr)
|
||||||
|
io.Copy(p.out, &p.strings)
|
||||||
|
io.Copy(p.out, &p.data0)
|
||||||
|
|
||||||
|
return p.out.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeIndex writes out an object index. mainIndex indicates whether
|
||||||
|
// we're writing out the main index, which is also read by
|
||||||
|
// non-compiler tools and includes a complete package description
|
||||||
|
// (i.e., name and height).
|
||||||
|
func (w *exportWriter) writeIndex(index map[types.Object]uint64) {
|
||||||
|
// Build a map from packages to objects from that package.
|
||||||
|
pkgObjs := map[*types.Package][]types.Object{}
|
||||||
|
|
||||||
|
// For the main index, make sure to include every package that
|
||||||
|
// we reference, even if we're not exporting (or reexporting)
|
||||||
|
// any symbols from it.
|
||||||
|
pkgObjs[w.p.localpkg] = nil
|
||||||
|
for pkg := range w.p.allPkgs {
|
||||||
|
pkgObjs[pkg] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for obj := range index {
|
||||||
|
pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []*types.Package
|
||||||
|
for pkg, objs := range pkgObjs {
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
|
||||||
|
sort.Slice(objs, func(i, j int) bool {
|
||||||
|
return objs[i].Name() < objs[j].Name()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(pkgs, func(i, j int) bool {
|
||||||
|
return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
w.uint64(uint64(len(pkgs)))
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
w.string(w.exportPath(pkg))
|
||||||
|
w.string(pkg.Name())
|
||||||
|
w.uint64(uint64(0)) // package height is not needed for go/types
|
||||||
|
|
||||||
|
objs := pkgObjs[pkg]
|
||||||
|
w.uint64(uint64(len(objs)))
|
||||||
|
for _, obj := range objs {
|
||||||
|
w.string(obj.Name())
|
||||||
|
w.uint64(index[obj])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type iexporter struct {
|
||||||
|
fset *token.FileSet
|
||||||
|
out *bytes.Buffer
|
||||||
|
|
||||||
|
localpkg *types.Package
|
||||||
|
|
||||||
|
// allPkgs tracks all packages that have been referenced by
|
||||||
|
// the export data, so we can ensure to include them in the
|
||||||
|
// main index.
|
||||||
|
allPkgs map[*types.Package]bool
|
||||||
|
|
||||||
|
declTodo objQueue
|
||||||
|
|
||||||
|
strings intWriter
|
||||||
|
stringIndex map[string]uint64
|
||||||
|
|
||||||
|
data0 intWriter
|
||||||
|
declIndex map[types.Object]uint64
|
||||||
|
typIndex map[types.Type]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringOff returns the offset of s within the string section.
|
||||||
|
// If not already present, it's added to the end.
|
||||||
|
func (p *iexporter) stringOff(s string) uint64 {
|
||||||
|
off, ok := p.stringIndex[s]
|
||||||
|
if !ok {
|
||||||
|
off = uint64(p.strings.Len())
|
||||||
|
p.stringIndex[s] = off
|
||||||
|
|
||||||
|
p.strings.uint64(uint64(len(s)))
|
||||||
|
p.strings.WriteString(s)
|
||||||
|
}
|
||||||
|
return off
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushDecl adds n to the declaration work queue, if not already present.
|
||||||
|
func (p *iexporter) pushDecl(obj types.Object) {
|
||||||
|
// Package unsafe is known to the compiler and predeclared.
|
||||||
|
assert(obj.Pkg() != types.Unsafe)
|
||||||
|
|
||||||
|
if _, ok := p.declIndex[obj]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.declIndex[obj] = ^uint64(0) // mark n present in work queue
|
||||||
|
p.declTodo.pushTail(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exportWriter handles writing out individual data section chunks.
|
||||||
|
type exportWriter struct {
|
||||||
|
p *iexporter
|
||||||
|
|
||||||
|
data intWriter
|
||||||
|
currPkg *types.Package
|
||||||
|
prevFile string
|
||||||
|
prevLine int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) exportPath(pkg *types.Package) string {
|
||||||
|
if pkg == w.p.localpkg {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pkg.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iexporter) doDecl(obj types.Object) {
|
||||||
|
w := p.newWriter()
|
||||||
|
w.setPkg(obj.Pkg(), false)
|
||||||
|
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case *types.Var:
|
||||||
|
w.tag('V')
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
w.typ(obj.Type(), obj.Pkg())
|
||||||
|
|
||||||
|
case *types.Func:
|
||||||
|
sig, _ := obj.Type().(*types.Signature)
|
||||||
|
if sig.Recv() != nil {
|
||||||
|
panic(internalErrorf("unexpected method: %v", sig))
|
||||||
|
}
|
||||||
|
w.tag('F')
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
w.signature(sig)
|
||||||
|
|
||||||
|
case *types.Const:
|
||||||
|
w.tag('C')
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
w.value(obj.Type(), obj.Val())
|
||||||
|
|
||||||
|
case *types.TypeName:
|
||||||
|
if obj.IsAlias() {
|
||||||
|
w.tag('A')
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
w.typ(obj.Type(), obj.Pkg())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined type.
|
||||||
|
w.tag('T')
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
|
||||||
|
underlying := obj.Type().Underlying()
|
||||||
|
w.typ(underlying, obj.Pkg())
|
||||||
|
|
||||||
|
t := obj.Type()
|
||||||
|
if types.IsInterface(t) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
named, ok := t.(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
panic(internalErrorf("%s is not a defined type", t))
|
||||||
|
}
|
||||||
|
|
||||||
|
n := named.NumMethods()
|
||||||
|
w.uint64(uint64(n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
m := named.Method(i)
|
||||||
|
w.pos(m.Pos())
|
||||||
|
w.string(m.Name())
|
||||||
|
sig, _ := m.Type().(*types.Signature)
|
||||||
|
w.param(sig.Recv())
|
||||||
|
w.signature(sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected object: %v", obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.declIndex[obj] = w.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) tag(tag byte) {
|
||||||
|
w.data.WriteByte(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) pos(pos token.Pos) {
|
||||||
|
if w.p.fset == nil {
|
||||||
|
w.int64(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := w.p.fset.Position(pos)
|
||||||
|
file := p.Filename
|
||||||
|
line := int64(p.Line)
|
||||||
|
|
||||||
|
// When file is the same as the last position (common case),
|
||||||
|
// we can save a few bytes by delta encoding just the line
|
||||||
|
// number.
|
||||||
|
//
|
||||||
|
// Note: Because data objects may be read out of order (or not
|
||||||
|
// at all), we can only apply delta encoding within a single
|
||||||
|
// object. This is handled implicitly by tracking prevFile and
|
||||||
|
// prevLine as fields of exportWriter.
|
||||||
|
|
||||||
|
if file == w.prevFile {
|
||||||
|
delta := line - w.prevLine
|
||||||
|
w.int64(delta)
|
||||||
|
if delta == deltaNewFile {
|
||||||
|
w.int64(-1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.int64(deltaNewFile)
|
||||||
|
w.int64(line) // line >= 0
|
||||||
|
w.string(file)
|
||||||
|
w.prevFile = file
|
||||||
|
}
|
||||||
|
w.prevLine = line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) pkg(pkg *types.Package) {
|
||||||
|
// Ensure any referenced packages are declared in the main index.
|
||||||
|
w.p.allPkgs[pkg] = true
|
||||||
|
|
||||||
|
w.string(w.exportPath(pkg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) qualifiedIdent(obj types.Object) {
|
||||||
|
// Ensure any referenced declarations are written out too.
|
||||||
|
w.p.pushDecl(obj)
|
||||||
|
|
||||||
|
w.string(obj.Name())
|
||||||
|
w.pkg(obj.Pkg())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
|
||||||
|
w.data.uint64(w.p.typOff(t, pkg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iexporter) newWriter() *exportWriter {
|
||||||
|
return &exportWriter{p: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) flush() uint64 {
|
||||||
|
off := uint64(w.p.data0.Len())
|
||||||
|
io.Copy(&w.p.data0, &w.data)
|
||||||
|
return off
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 {
|
||||||
|
off, ok := p.typIndex[t]
|
||||||
|
if !ok {
|
||||||
|
w := p.newWriter()
|
||||||
|
w.doTyp(t, pkg)
|
||||||
|
off = predeclReserved + w.flush()
|
||||||
|
p.typIndex[t] = off
|
||||||
|
}
|
||||||
|
return off
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) startType(k itag) {
|
||||||
|
w.data.uint64(uint64(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.Named:
|
||||||
|
w.startType(definedType)
|
||||||
|
w.qualifiedIdent(t.Obj())
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
w.startType(pointerType)
|
||||||
|
w.typ(t.Elem(), pkg)
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
w.startType(sliceType)
|
||||||
|
w.typ(t.Elem(), pkg)
|
||||||
|
|
||||||
|
case *types.Array:
|
||||||
|
w.startType(arrayType)
|
||||||
|
w.uint64(uint64(t.Len()))
|
||||||
|
w.typ(t.Elem(), pkg)
|
||||||
|
|
||||||
|
case *types.Chan:
|
||||||
|
w.startType(chanType)
|
||||||
|
// 1 RecvOnly; 2 SendOnly; 3 SendRecv
|
||||||
|
var dir uint64
|
||||||
|
switch t.Dir() {
|
||||||
|
case types.RecvOnly:
|
||||||
|
dir = 1
|
||||||
|
case types.SendOnly:
|
||||||
|
dir = 2
|
||||||
|
case types.SendRecv:
|
||||||
|
dir = 3
|
||||||
|
}
|
||||||
|
w.uint64(dir)
|
||||||
|
w.typ(t.Elem(), pkg)
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
w.startType(mapType)
|
||||||
|
w.typ(t.Key(), pkg)
|
||||||
|
w.typ(t.Elem(), pkg)
|
||||||
|
|
||||||
|
case *types.Signature:
|
||||||
|
w.startType(signatureType)
|
||||||
|
w.setPkg(pkg, true)
|
||||||
|
w.signature(t)
|
||||||
|
|
||||||
|
case *types.Struct:
|
||||||
|
w.startType(structType)
|
||||||
|
w.setPkg(pkg, true)
|
||||||
|
|
||||||
|
n := t.NumFields()
|
||||||
|
w.uint64(uint64(n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
w.pos(f.Pos())
|
||||||
|
w.string(f.Name())
|
||||||
|
w.typ(f.Type(), pkg)
|
||||||
|
w.bool(f.Anonymous())
|
||||||
|
w.string(t.Tag(i)) // note (or tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.Interface:
|
||||||
|
w.startType(interfaceType)
|
||||||
|
w.setPkg(pkg, true)
|
||||||
|
|
||||||
|
n := t.NumEmbeddeds()
|
||||||
|
w.uint64(uint64(n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
f := t.Embedded(i)
|
||||||
|
w.pos(f.Obj().Pos())
|
||||||
|
w.typ(f.Obj().Type(), f.Obj().Pkg())
|
||||||
|
}
|
||||||
|
|
||||||
|
n = t.NumExplicitMethods()
|
||||||
|
w.uint64(uint64(n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
m := t.ExplicitMethod(i)
|
||||||
|
w.pos(m.Pos())
|
||||||
|
w.string(m.Name())
|
||||||
|
sig, _ := m.Type().(*types.Signature)
|
||||||
|
w.signature(sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
|
||||||
|
if write {
|
||||||
|
w.pkg(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.currPkg = pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) signature(sig *types.Signature) {
|
||||||
|
w.paramList(sig.Params())
|
||||||
|
w.paramList(sig.Results())
|
||||||
|
if sig.Params().Len() > 0 {
|
||||||
|
w.bool(sig.Variadic())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) paramList(tup *types.Tuple) {
|
||||||
|
n := tup.Len()
|
||||||
|
w.uint64(uint64(n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
w.param(tup.At(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) param(obj types.Object) {
|
||||||
|
w.pos(obj.Pos())
|
||||||
|
w.localIdent(obj)
|
||||||
|
w.typ(obj.Type(), obj.Pkg())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) value(typ types.Type, v constant.Value) {
|
||||||
|
w.typ(typ, nil)
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case constant.Bool:
|
||||||
|
w.bool(constant.BoolVal(v))
|
||||||
|
case constant.Int:
|
||||||
|
var i big.Int
|
||||||
|
if i64, exact := constant.Int64Val(v); exact {
|
||||||
|
i.SetInt64(i64)
|
||||||
|
} else if ui64, exact := constant.Uint64Val(v); exact {
|
||||||
|
i.SetUint64(ui64)
|
||||||
|
} else {
|
||||||
|
i.SetString(v.ExactString(), 10)
|
||||||
|
}
|
||||||
|
w.mpint(&i, typ)
|
||||||
|
case constant.Float:
|
||||||
|
f := constantToFloat(v)
|
||||||
|
w.mpfloat(f, typ)
|
||||||
|
case constant.Complex:
|
||||||
|
w.mpfloat(constantToFloat(constant.Real(v)), typ)
|
||||||
|
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
|
||||||
|
case constant.String:
|
||||||
|
w.string(constant.StringVal(v))
|
||||||
|
case constant.Unknown:
|
||||||
|
// package contains type errors
|
||||||
|
default:
|
||||||
|
panic(internalErrorf("unexpected value %v (%T)", v, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// constantToFloat converts a constant.Value with kind constant.Float to a
|
||||||
|
// big.Float.
|
||||||
|
func constantToFloat(x constant.Value) *big.Float {
|
||||||
|
assert(x.Kind() == constant.Float)
|
||||||
|
// Use the same floating-point precision (512) as cmd/compile
|
||||||
|
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
|
||||||
|
const mpprec = 512
|
||||||
|
var f big.Float
|
||||||
|
f.SetPrec(mpprec)
|
||||||
|
if v, exact := constant.Float64Val(x); exact {
|
||||||
|
// float64
|
||||||
|
f.SetFloat64(v)
|
||||||
|
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||||
|
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||||
|
n := valueToRat(num)
|
||||||
|
d := valueToRat(denom)
|
||||||
|
f.SetRat(n.Quo(n, d))
|
||||||
|
} else {
|
||||||
|
// Value too large to represent as a fraction => inaccessible.
|
||||||
|
// TODO(gri): add big.Float accessor to constant.Value.
|
||||||
|
_, ok := f.SetString(x.ExactString())
|
||||||
|
assert(ok)
|
||||||
|
}
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
// mpint exports a multi-precision integer.
|
||||||
|
//
|
||||||
|
// For unsigned types, small values are written out as a single
|
||||||
|
// byte. Larger values are written out as a length-prefixed big-endian
|
||||||
|
// byte string, where the length prefix is encoded as its complement.
|
||||||
|
// For example, bytes 0, 1, and 2 directly represent the integer
|
||||||
|
// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-,
|
||||||
|
// 2-, and 3-byte big-endian string follow.
|
||||||
|
//
|
||||||
|
// Encoding for signed types use the same general approach as for
|
||||||
|
// unsigned types, except small values use zig-zag encoding and the
|
||||||
|
// bottom bit of length prefix byte for large values is reserved as a
|
||||||
|
// sign bit.
|
||||||
|
//
|
||||||
|
// The exact boundary between small and large encodings varies
|
||||||
|
// according to the maximum number of bytes needed to encode a value
|
||||||
|
// of type typ. As a special case, 8-bit types are always encoded as a
|
||||||
|
// single byte.
|
||||||
|
//
|
||||||
|
// TODO(mdempsky): Is this level of complexity really worthwhile?
|
||||||
|
func (w *exportWriter) mpint(x *big.Int, typ types.Type) {
|
||||||
|
basic, ok := typ.Underlying().(*types.Basic)
|
||||||
|
if !ok {
|
||||||
|
panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying()))
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, maxBytes := intSize(basic)
|
||||||
|
|
||||||
|
negative := x.Sign() < 0
|
||||||
|
if !signed && negative {
|
||||||
|
panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := x.Bytes()
|
||||||
|
if len(b) > 0 && b[0] == 0 {
|
||||||
|
panic(internalErrorf("leading zeros"))
|
||||||
|
}
|
||||||
|
if uint(len(b)) > maxBytes {
|
||||||
|
panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSmall := 256 - maxBytes
|
||||||
|
if signed {
|
||||||
|
maxSmall = 256 - 2*maxBytes
|
||||||
|
}
|
||||||
|
if maxBytes == 1 {
|
||||||
|
maxSmall = 256
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if x can use small value encoding.
|
||||||
|
if len(b) <= 1 {
|
||||||
|
var ux uint
|
||||||
|
if len(b) == 1 {
|
||||||
|
ux = uint(b[0])
|
||||||
|
}
|
||||||
|
if signed {
|
||||||
|
ux <<= 1
|
||||||
|
if negative {
|
||||||
|
ux--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ux < maxSmall {
|
||||||
|
w.data.WriteByte(byte(ux))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 256 - uint(len(b))
|
||||||
|
if signed {
|
||||||
|
n = 256 - 2*uint(len(b))
|
||||||
|
if negative {
|
||||||
|
n |= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n < maxSmall || n >= 256 {
|
||||||
|
panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.data.WriteByte(byte(n))
|
||||||
|
w.data.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mpfloat exports a multi-precision floating point number.
|
||||||
|
//
|
||||||
|
// The number's value is decomposed into mantissa × 2**exponent, where
|
||||||
|
// mantissa is an integer. The value is written out as mantissa (as a
|
||||||
|
// multi-precision integer) and then the exponent, except exponent is
|
||||||
|
// omitted if mantissa is zero.
|
||||||
|
func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) {
|
||||||
|
if f.IsInf() {
|
||||||
|
panic("infinite constant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break into f = mant × 2**exp, with 0.5 <= mant < 1.
|
||||||
|
var mant big.Float
|
||||||
|
exp := int64(f.MantExp(&mant))
|
||||||
|
|
||||||
|
// Scale so that mant is an integer.
|
||||||
|
prec := mant.MinPrec()
|
||||||
|
mant.SetMantExp(&mant, int(prec))
|
||||||
|
exp -= int64(prec)
|
||||||
|
|
||||||
|
manti, acc := mant.Int(nil)
|
||||||
|
if acc != big.Exact {
|
||||||
|
panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc))
|
||||||
|
}
|
||||||
|
w.mpint(manti, typ)
|
||||||
|
if manti.Sign() != 0 {
|
||||||
|
w.int64(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) bool(b bool) bool {
|
||||||
|
var x uint64
|
||||||
|
if b {
|
||||||
|
x = 1
|
||||||
|
}
|
||||||
|
w.uint64(x)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *exportWriter) int64(x int64) { w.data.int64(x) }
|
||||||
|
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
|
||||||
|
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }
|
||||||
|
|
||||||
|
func (w *exportWriter) localIdent(obj types.Object) {
|
||||||
|
// Anonymous parameters.
|
||||||
|
if obj == nil {
|
||||||
|
w.string("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := obj.Name()
|
||||||
|
if name == "_" {
|
||||||
|
w.string("_")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.string(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type intWriter struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *intWriter) int64(x int64) {
|
||||||
|
var buf [binary.MaxVarintLen64]byte
|
||||||
|
n := binary.PutVarint(buf[:], x)
|
||||||
|
w.Write(buf[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *intWriter) uint64(x uint64) {
|
||||||
|
var buf [binary.MaxVarintLen64]byte
|
||||||
|
n := binary.PutUvarint(buf[:], x)
|
||||||
|
w.Write(buf[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func assert(cond bool) {
|
||||||
|
if !cond {
|
||||||
|
panic("internal error: assertion failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The below is copied from go/src/cmd/compile/internal/gc/syntax.go.
|
||||||
|
|
||||||
|
// objQueue is a FIFO queue of types.Object. The zero value of objQueue is
|
||||||
|
// a ready-to-use empty queue.
|
||||||
|
type objQueue struct {
|
||||||
|
ring []types.Object
|
||||||
|
head, tail int
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty returns true if q contains no Nodes.
|
||||||
|
func (q *objQueue) empty() bool {
|
||||||
|
return q.head == q.tail
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushTail appends n to the tail of the queue.
|
||||||
|
func (q *objQueue) pushTail(obj types.Object) {
|
||||||
|
if len(q.ring) == 0 {
|
||||||
|
q.ring = make([]types.Object, 16)
|
||||||
|
} else if q.head+len(q.ring) == q.tail {
|
||||||
|
// Grow the ring.
|
||||||
|
nring := make([]types.Object, len(q.ring)*2)
|
||||||
|
// Copy the old elements.
|
||||||
|
part := q.ring[q.head%len(q.ring):]
|
||||||
|
if q.tail-q.head <= len(part) {
|
||||||
|
part = part[:q.tail-q.head]
|
||||||
|
copy(nring, part)
|
||||||
|
} else {
|
||||||
|
pos := copy(nring, part)
|
||||||
|
copy(nring[pos:], q.ring[:q.tail%len(q.ring)])
|
||||||
|
}
|
||||||
|
q.ring, q.head, q.tail = nring, 0, q.tail-q.head
|
||||||
|
}
|
||||||
|
|
||||||
|
q.ring[q.tail%len(q.ring)] = obj
|
||||||
|
q.tail++
|
||||||
|
}
|
||||||
|
|
||||||
|
// popHead pops a node from the head of the queue. It panics if q is empty.
|
||||||
|
func (q *objQueue) popHead() types.Object {
|
||||||
|
if q.empty() {
|
||||||
|
panic("dequeue empty")
|
||||||
|
}
|
||||||
|
obj := q.ring[q.head%len(q.ring)]
|
||||||
|
q.head++
|
||||||
|
return obj
|
||||||
|
}
|
|
@ -0,0 +1,630 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Indexed package import.
|
||||||
|
// See cmd/compile/internal/gc/iexport.go for the export data format.
|
||||||
|
|
||||||
|
// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go.
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"go/constant"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type intReader struct {
|
||||||
|
*bytes.Reader
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *intReader) int64() int64 {
|
||||||
|
i, err := binary.ReadVarint(r.Reader)
|
||||||
|
if err != nil {
|
||||||
|
errorf("import %q: read varint error: %v", r.path, err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *intReader) uint64() uint64 {
|
||||||
|
i, err := binary.ReadUvarint(r.Reader)
|
||||||
|
if err != nil {
|
||||||
|
errorf("import %q: read varint error: %v", r.path, err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
const predeclReserved = 32
|
||||||
|
|
||||||
|
type itag uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Types
|
||||||
|
definedType itag = iota
|
||||||
|
pointerType
|
||||||
|
sliceType
|
||||||
|
arrayType
|
||||||
|
chanType
|
||||||
|
mapType
|
||||||
|
signatureType
|
||||||
|
structType
|
||||||
|
interfaceType
|
||||||
|
)
|
||||||
|
|
||||||
|
// IImportData imports a package from the serialized package data
|
||||||
|
// and returns the number of bytes consumed and a reference to the package.
|
||||||
|
// If the export data version is not recognized or the format is otherwise
|
||||||
|
// compromised, an error is returned.
|
||||||
|
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
|
||||||
|
const currentVersion = 1
|
||||||
|
version := int64(-1)
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
if version > currentVersion {
|
||||||
|
err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
r := &intReader{bytes.NewReader(data), path}
|
||||||
|
|
||||||
|
version = int64(r.uint64())
|
||||||
|
switch version {
|
||||||
|
case currentVersion, 0:
|
||||||
|
default:
|
||||||
|
errorf("unknown iexport format version %d", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
sLen := int64(r.uint64())
|
||||||
|
dLen := int64(r.uint64())
|
||||||
|
|
||||||
|
whence, _ := r.Seek(0, io.SeekCurrent)
|
||||||
|
stringData := data[whence : whence+sLen]
|
||||||
|
declData := data[whence+sLen : whence+sLen+dLen]
|
||||||
|
r.Seek(sLen+dLen, io.SeekCurrent)
|
||||||
|
|
||||||
|
p := iimporter{
|
||||||
|
ipath: path,
|
||||||
|
version: int(version),
|
||||||
|
|
||||||
|
stringData: stringData,
|
||||||
|
stringCache: make(map[uint64]string),
|
||||||
|
pkgCache: make(map[uint64]*types.Package),
|
||||||
|
|
||||||
|
declData: declData,
|
||||||
|
pkgIndex: make(map[*types.Package]map[string]uint64),
|
||||||
|
typCache: make(map[uint64]types.Type),
|
||||||
|
|
||||||
|
fake: fakeFileSet{
|
||||||
|
fset: fset,
|
||||||
|
files: make(map[string]*token.File),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, pt := range predeclared() {
|
||||||
|
p.typCache[uint64(i)] = pt
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgList := make([]*types.Package, r.uint64())
|
||||||
|
for i := range pkgList {
|
||||||
|
pkgPathOff := r.uint64()
|
||||||
|
pkgPath := p.stringAt(pkgPathOff)
|
||||||
|
pkgName := p.stringAt(r.uint64())
|
||||||
|
_ = r.uint64() // package height; unused by go/types
|
||||||
|
|
||||||
|
if pkgPath == "" {
|
||||||
|
pkgPath = path
|
||||||
|
}
|
||||||
|
pkg := imports[pkgPath]
|
||||||
|
if pkg == nil {
|
||||||
|
pkg = types.NewPackage(pkgPath, pkgName)
|
||||||
|
imports[pkgPath] = pkg
|
||||||
|
} else if pkg.Name() != pkgName {
|
||||||
|
errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pkgCache[pkgPathOff] = pkg
|
||||||
|
|
||||||
|
nameIndex := make(map[string]uint64)
|
||||||
|
for nSyms := r.uint64(); nSyms > 0; nSyms-- {
|
||||||
|
name := p.stringAt(r.uint64())
|
||||||
|
nameIndex[name] = r.uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pkgIndex[pkg] = nameIndex
|
||||||
|
pkgList[i] = pkg
|
||||||
|
}
|
||||||
|
if len(pkgList) == 0 {
|
||||||
|
errorf("no packages found for %s", path)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
p.ipkg = pkgList[0]
|
||||||
|
names := make([]string, 0, len(p.pkgIndex[p.ipkg]))
|
||||||
|
for name := range p.pkgIndex[p.ipkg] {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
p.doDecl(p.ipkg, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, typ := range p.interfaceList {
|
||||||
|
typ.Complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// record all referenced packages as imports
|
||||||
|
list := append(([]*types.Package)(nil), pkgList[1:]...)
|
||||||
|
sort.Sort(byPath(list))
|
||||||
|
p.ipkg.SetImports(list)
|
||||||
|
|
||||||
|
// package was imported completely and without errors
|
||||||
|
p.ipkg.MarkComplete()
|
||||||
|
|
||||||
|
consumed, _ := r.Seek(0, io.SeekCurrent)
|
||||||
|
return int(consumed), p.ipkg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iimporter struct {
|
||||||
|
ipath string
|
||||||
|
ipkg *types.Package
|
||||||
|
version int
|
||||||
|
|
||||||
|
stringData []byte
|
||||||
|
stringCache map[uint64]string
|
||||||
|
pkgCache map[uint64]*types.Package
|
||||||
|
|
||||||
|
declData []byte
|
||||||
|
pkgIndex map[*types.Package]map[string]uint64
|
||||||
|
typCache map[uint64]types.Type
|
||||||
|
|
||||||
|
fake fakeFileSet
|
||||||
|
interfaceList []*types.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iimporter) doDecl(pkg *types.Package, name string) {
|
||||||
|
// See if we've already imported this declaration.
|
||||||
|
if obj := pkg.Scope().Lookup(name); obj != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
off, ok := p.pkgIndex[pkg][name]
|
||||||
|
if !ok {
|
||||||
|
errorf("%v.%v not in index", pkg, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &importReader{p: p, currPkg: pkg}
|
||||||
|
r.declReader.Reset(p.declData[off:])
|
||||||
|
|
||||||
|
r.obj(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iimporter) stringAt(off uint64) string {
|
||||||
|
if s, ok := p.stringCache[off]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
slen, n := binary.Uvarint(p.stringData[off:])
|
||||||
|
if n <= 0 {
|
||||||
|
errorf("varint failed")
|
||||||
|
}
|
||||||
|
spos := off + uint64(n)
|
||||||
|
s := string(p.stringData[spos : spos+slen])
|
||||||
|
p.stringCache[off] = s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iimporter) pkgAt(off uint64) *types.Package {
|
||||||
|
if pkg, ok := p.pkgCache[off]; ok {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
path := p.stringAt(off)
|
||||||
|
if path == p.ipath {
|
||||||
|
return p.ipkg
|
||||||
|
}
|
||||||
|
errorf("missing package %q in %q", path, p.ipath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iimporter) typAt(off uint64, base *types.Named) types.Type {
|
||||||
|
if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
if off < predeclReserved {
|
||||||
|
errorf("predeclared type missing from cache: %v", off)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &importReader{p: p}
|
||||||
|
r.declReader.Reset(p.declData[off-predeclReserved:])
|
||||||
|
t := r.doType(base)
|
||||||
|
|
||||||
|
if base == nil || !isInterface(t) {
|
||||||
|
p.typCache[off] = t
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
type importReader struct {
|
||||||
|
p *iimporter
|
||||||
|
declReader bytes.Reader
|
||||||
|
currPkg *types.Package
|
||||||
|
prevFile string
|
||||||
|
prevLine int64
|
||||||
|
prevColumn int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) obj(name string) {
|
||||||
|
tag := r.byte()
|
||||||
|
pos := r.pos()
|
||||||
|
|
||||||
|
switch tag {
|
||||||
|
case 'A':
|
||||||
|
typ := r.typ()
|
||||||
|
|
||||||
|
r.declare(types.NewTypeName(pos, r.currPkg, name, typ))
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
typ, val := r.value()
|
||||||
|
|
||||||
|
r.declare(types.NewConst(pos, r.currPkg, name, typ, val))
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
sig := r.signature(nil)
|
||||||
|
|
||||||
|
r.declare(types.NewFunc(pos, r.currPkg, name, sig))
|
||||||
|
|
||||||
|
case 'T':
|
||||||
|
// Types can be recursive. We need to setup a stub
|
||||||
|
// declaration before recursing.
|
||||||
|
obj := types.NewTypeName(pos, r.currPkg, name, nil)
|
||||||
|
named := types.NewNamed(obj, nil, nil)
|
||||||
|
r.declare(obj)
|
||||||
|
|
||||||
|
underlying := r.p.typAt(r.uint64(), named).Underlying()
|
||||||
|
named.SetUnderlying(underlying)
|
||||||
|
|
||||||
|
if !isInterface(underlying) {
|
||||||
|
for n := r.uint64(); n > 0; n-- {
|
||||||
|
mpos := r.pos()
|
||||||
|
mname := r.ident()
|
||||||
|
recv := r.param()
|
||||||
|
msig := r.signature(recv)
|
||||||
|
|
||||||
|
named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'V':
|
||||||
|
typ := r.typ()
|
||||||
|
|
||||||
|
r.declare(types.NewVar(pos, r.currPkg, name, typ))
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("unexpected tag: %v", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) declare(obj types.Object) {
|
||||||
|
obj.Pkg().Scope().Insert(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) value() (typ types.Type, val constant.Value) {
|
||||||
|
typ = r.typ()
|
||||||
|
|
||||||
|
switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
|
||||||
|
case types.IsBoolean:
|
||||||
|
val = constant.MakeBool(r.bool())
|
||||||
|
|
||||||
|
case types.IsString:
|
||||||
|
val = constant.MakeString(r.string())
|
||||||
|
|
||||||
|
case types.IsInteger:
|
||||||
|
val = r.mpint(b)
|
||||||
|
|
||||||
|
case types.IsFloat:
|
||||||
|
val = r.mpfloat(b)
|
||||||
|
|
||||||
|
case types.IsComplex:
|
||||||
|
re := r.mpfloat(b)
|
||||||
|
im := r.mpfloat(b)
|
||||||
|
val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im))
|
||||||
|
|
||||||
|
default:
|
||||||
|
if b.Kind() == types.Invalid {
|
||||||
|
val = constant.MakeUnknown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errorf("unexpected type %v", typ) // panics
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intSize(b *types.Basic) (signed bool, maxBytes uint) {
|
||||||
|
if (b.Info() & types.IsUntyped) != 0 {
|
||||||
|
return true, 64
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b.Kind() {
|
||||||
|
case types.Float32, types.Complex64:
|
||||||
|
return true, 3
|
||||||
|
case types.Float64, types.Complex128:
|
||||||
|
return true, 7
|
||||||
|
}
|
||||||
|
|
||||||
|
signed = (b.Info() & types.IsUnsigned) == 0
|
||||||
|
switch b.Kind() {
|
||||||
|
case types.Int8, types.Uint8:
|
||||||
|
maxBytes = 1
|
||||||
|
case types.Int16, types.Uint16:
|
||||||
|
maxBytes = 2
|
||||||
|
case types.Int32, types.Uint32:
|
||||||
|
maxBytes = 4
|
||||||
|
default:
|
||||||
|
maxBytes = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) mpint(b *types.Basic) constant.Value {
|
||||||
|
signed, maxBytes := intSize(b)
|
||||||
|
|
||||||
|
maxSmall := 256 - maxBytes
|
||||||
|
if signed {
|
||||||
|
maxSmall = 256 - 2*maxBytes
|
||||||
|
}
|
||||||
|
if maxBytes == 1 {
|
||||||
|
maxSmall = 256
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ := r.declReader.ReadByte()
|
||||||
|
if uint(n) < maxSmall {
|
||||||
|
v := int64(n)
|
||||||
|
if signed {
|
||||||
|
v >>= 1
|
||||||
|
if n&1 != 0 {
|
||||||
|
v = ^v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constant.MakeInt64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := -n
|
||||||
|
if signed {
|
||||||
|
v = -(n &^ 1) >> 1
|
||||||
|
}
|
||||||
|
if v < 1 || uint(v) > maxBytes {
|
||||||
|
errorf("weird decoding: %v, %v => %v", n, signed, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, v)
|
||||||
|
io.ReadFull(&r.declReader, buf)
|
||||||
|
|
||||||
|
// convert to little endian
|
||||||
|
// TODO(gri) go/constant should have a more direct conversion function
|
||||||
|
// (e.g., once it supports a big.Float based implementation)
|
||||||
|
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
buf[i], buf[j] = buf[j], buf[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
x := constant.MakeFromBytes(buf)
|
||||||
|
if signed && n&1 != 0 {
|
||||||
|
x = constant.UnaryOp(token.SUB, x, 0)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) mpfloat(b *types.Basic) constant.Value {
|
||||||
|
x := r.mpint(b)
|
||||||
|
if constant.Sign(x) == 0 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := r.int64()
|
||||||
|
switch {
|
||||||
|
case exp > 0:
|
||||||
|
x = constant.Shift(x, token.SHL, uint(exp))
|
||||||
|
case exp < 0:
|
||||||
|
d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp))
|
||||||
|
x = constant.BinaryOp(x, token.QUO, d)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) ident() string {
|
||||||
|
return r.string()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) qualifiedIdent() (*types.Package, string) {
|
||||||
|
name := r.string()
|
||||||
|
pkg := r.pkg()
|
||||||
|
return pkg, name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) pos() token.Pos {
|
||||||
|
if r.p.version >= 1 {
|
||||||
|
r.posv1()
|
||||||
|
} else {
|
||||||
|
r.posv0()
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 {
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) posv0() {
|
||||||
|
delta := r.int64()
|
||||||
|
if delta != deltaNewFile {
|
||||||
|
r.prevLine += delta
|
||||||
|
} else if l := r.int64(); l == -1 {
|
||||||
|
r.prevLine += deltaNewFile
|
||||||
|
} else {
|
||||||
|
r.prevFile = r.string()
|
||||||
|
r.prevLine = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) posv1() {
|
||||||
|
delta := r.int64()
|
||||||
|
r.prevColumn += delta >> 1
|
||||||
|
if delta&1 != 0 {
|
||||||
|
delta = r.int64()
|
||||||
|
r.prevLine += delta >> 1
|
||||||
|
if delta&1 != 0 {
|
||||||
|
r.prevFile = r.string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) typ() types.Type {
|
||||||
|
return r.p.typAt(r.uint64(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInterface(t types.Type) bool {
|
||||||
|
_, ok := t.(*types.Interface)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) }
|
||||||
|
func (r *importReader) string() string { return r.p.stringAt(r.uint64()) }
|
||||||
|
|
||||||
|
func (r *importReader) doType(base *types.Named) types.Type {
|
||||||
|
switch k := r.kind(); k {
|
||||||
|
default:
|
||||||
|
errorf("unexpected kind tag in %q: %v", r.p.ipath, k)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case definedType:
|
||||||
|
pkg, name := r.qualifiedIdent()
|
||||||
|
r.p.doDecl(pkg, name)
|
||||||
|
return pkg.Scope().Lookup(name).(*types.TypeName).Type()
|
||||||
|
case pointerType:
|
||||||
|
return types.NewPointer(r.typ())
|
||||||
|
case sliceType:
|
||||||
|
return types.NewSlice(r.typ())
|
||||||
|
case arrayType:
|
||||||
|
n := r.uint64()
|
||||||
|
return types.NewArray(r.typ(), int64(n))
|
||||||
|
case chanType:
|
||||||
|
dir := chanDir(int(r.uint64()))
|
||||||
|
return types.NewChan(dir, r.typ())
|
||||||
|
case mapType:
|
||||||
|
return types.NewMap(r.typ(), r.typ())
|
||||||
|
case signatureType:
|
||||||
|
r.currPkg = r.pkg()
|
||||||
|
return r.signature(nil)
|
||||||
|
|
||||||
|
case structType:
|
||||||
|
r.currPkg = r.pkg()
|
||||||
|
|
||||||
|
fields := make([]*types.Var, r.uint64())
|
||||||
|
tags := make([]string, len(fields))
|
||||||
|
for i := range fields {
|
||||||
|
fpos := r.pos()
|
||||||
|
fname := r.ident()
|
||||||
|
ftyp := r.typ()
|
||||||
|
emb := r.bool()
|
||||||
|
tag := r.string()
|
||||||
|
|
||||||
|
fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
|
||||||
|
tags[i] = tag
|
||||||
|
}
|
||||||
|
return types.NewStruct(fields, tags)
|
||||||
|
|
||||||
|
case interfaceType:
|
||||||
|
r.currPkg = r.pkg()
|
||||||
|
|
||||||
|
embeddeds := make([]types.Type, r.uint64())
|
||||||
|
for i := range embeddeds {
|
||||||
|
_ = r.pos()
|
||||||
|
embeddeds[i] = r.typ()
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := make([]*types.Func, r.uint64())
|
||||||
|
for i := range methods {
|
||||||
|
mpos := r.pos()
|
||||||
|
mname := r.ident()
|
||||||
|
|
||||||
|
// TODO(mdempsky): Matches bimport.go, but I
|
||||||
|
// don't agree with this.
|
||||||
|
var recv *types.Var
|
||||||
|
if base != nil {
|
||||||
|
recv = types.NewVar(token.NoPos, r.currPkg, "", base)
|
||||||
|
}
|
||||||
|
|
||||||
|
msig := r.signature(recv)
|
||||||
|
methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig)
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := newInterface(methods, embeddeds)
|
||||||
|
r.p.interfaceList = append(r.p.interfaceList, typ)
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) kind() itag {
|
||||||
|
return itag(r.uint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) signature(recv *types.Var) *types.Signature {
|
||||||
|
params := r.paramList()
|
||||||
|
results := r.paramList()
|
||||||
|
variadic := params.Len() > 0 && r.bool()
|
||||||
|
return types.NewSignature(recv, params, results, variadic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) paramList() *types.Tuple {
|
||||||
|
xs := make([]*types.Var, r.uint64())
|
||||||
|
for i := range xs {
|
||||||
|
xs[i] = r.param()
|
||||||
|
}
|
||||||
|
return types.NewTuple(xs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) param() *types.Var {
|
||||||
|
pos := r.pos()
|
||||||
|
name := r.ident()
|
||||||
|
typ := r.typ()
|
||||||
|
return types.NewParam(pos, r.currPkg, name, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) bool() bool {
|
||||||
|
return r.uint64() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) int64() int64 {
|
||||||
|
n, err := binary.ReadVarint(&r.declReader)
|
||||||
|
if err != nil {
|
||||||
|
errorf("readVarint: %v", err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) uint64() uint64 {
|
||||||
|
n, err := binary.ReadUvarint(&r.declReader)
|
||||||
|
if err != nil {
|
||||||
|
errorf("readUvarint: %v", err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *importReader) byte() byte {
|
||||||
|
x, err := r.declReader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errorf("declReader.ReadByte: %v", err)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
21
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
generated
vendored
Normal file
21
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.11
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||||
|
named := make([]*types.Named, len(embeddeds))
|
||||||
|
for i, e := range embeddeds {
|
||||||
|
var ok bool
|
||||||
|
named[i], ok = e.(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.NewInterface(methods, named)
|
||||||
|
}
|
13
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.11
|
||||||
|
|
||||||
|
package gcimporter
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||||
|
return types.NewInterfaceType(methods, embeddeds)
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package packagesdriver fetches type sizes for go/packages and go/analysis.
|
||||||
|
package packagesdriver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug = false
|
||||||
|
|
||||||
|
func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||||
|
// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver.
|
||||||
|
const toolPrefix = "GOPACKAGESDRIVER="
|
||||||
|
tool := ""
|
||||||
|
for _, env := range env {
|
||||||
|
if val := strings.TrimPrefix(env, toolPrefix); val != env {
|
||||||
|
tool = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tool == "" {
|
||||||
|
var err error
|
||||||
|
tool, err = exec.LookPath("gopackagesdriver")
|
||||||
|
if err != nil {
|
||||||
|
// We did not find the driver, so use "go list".
|
||||||
|
tool = "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tool == "off" {
|
||||||
|
return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := json.Marshal(struct {
|
||||||
|
Command string `json:"command"`
|
||||||
|
Env []string `json:"env"`
|
||||||
|
BuildFlags []string `json:"build_flags"`
|
||||||
|
}{
|
||||||
|
Command: "sizes",
|
||||||
|
Env: env,
|
||||||
|
BuildFlags: buildFlags,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := exec.CommandContext(ctx, tool)
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Env = env
|
||||||
|
cmd.Stdin = bytes.NewReader(req)
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||||
|
}
|
||||||
|
var response struct {
|
||||||
|
// Sizes, if not nil, is the types.Sizes to use when type checking.
|
||||||
|
Sizes *types.StdSizes
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.Sizes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||||
|
inv := gocommand.Invocation{
|
||||||
|
Verb: "list",
|
||||||
|
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
|
||||||
|
Env: env,
|
||||||
|
BuildFlags: buildFlags,
|
||||||
|
WorkingDir: dir,
|
||||||
|
}
|
||||||
|
stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv)
|
||||||
|
var goarch, compiler string
|
||||||
|
if rawErr != nil {
|
||||||
|
if strings.Contains(rawErr.Error(), "cannot find main module") {
|
||||||
|
// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
|
||||||
|
// TODO(matloob): Is this a problem in practice?
|
||||||
|
inv := gocommand.Invocation{
|
||||||
|
Verb: "env",
|
||||||
|
Args: []string{"GOARCH"},
|
||||||
|
Env: env,
|
||||||
|
WorkingDir: dir,
|
||||||
|
}
|
||||||
|
envout, enverr := gocmdRunner.Run(ctx, inv)
|
||||||
|
if enverr != nil {
|
||||||
|
return nil, enverr
|
||||||
|
}
|
||||||
|
goarch = strings.TrimSpace(envout.String())
|
||||||
|
compiler = "gc"
|
||||||
|
} else {
|
||||||
|
return nil, friendlyErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fields := strings.Fields(stdout.String())
|
||||||
|
if len(fields) < 2 {
|
||||||
|
return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
|
||||||
|
stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
goarch = fields[0]
|
||||||
|
compiler = fields[1]
|
||||||
|
}
|
||||||
|
return types.SizesFor(compiler, goarch), nil
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package packages loads Go packages for inspection and analysis.
|
||||||
|
|
||||||
|
The Load function takes as input a list of patterns and return a list of Package
|
||||||
|
structs describing individual packages matched by those patterns.
|
||||||
|
The LoadMode controls the amount of detail in the loaded packages.
|
||||||
|
|
||||||
|
Load passes most patterns directly to the underlying build tool,
|
||||||
|
but all patterns with the prefix "query=", where query is a
|
||||||
|
non-empty string of letters from [a-z], are reserved and may be
|
||||||
|
interpreted as query operators.
|
||||||
|
|
||||||
|
Two query operators are currently supported: "file" and "pattern".
|
||||||
|
|
||||||
|
The query "file=path/to/file.go" matches the package or packages enclosing
|
||||||
|
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
|
||||||
|
might return the packages "fmt" and "fmt [fmt.test]".
|
||||||
|
|
||||||
|
The query "pattern=string" causes "string" to be passed directly to
|
||||||
|
the underlying build tool. In most cases this is unnecessary,
|
||||||
|
but an application can use Load("pattern=" + x) as an escaping mechanism
|
||||||
|
to ensure that x is not interpreted as a query operator if it contains '='.
|
||||||
|
|
||||||
|
All other query operators are reserved for future use and currently
|
||||||
|
cause Load to report an error.
|
||||||
|
|
||||||
|
The Package struct provides basic information about the package, including
|
||||||
|
|
||||||
|
- ID, a unique identifier for the package in the returned set;
|
||||||
|
- GoFiles, the names of the package's Go source files;
|
||||||
|
- Imports, a map from source import strings to the Packages they name;
|
||||||
|
- Types, the type information for the package's exported symbols;
|
||||||
|
- Syntax, the parsed syntax trees for the package's source code; and
|
||||||
|
- TypeInfo, the result of a complete type-check of the package syntax trees.
|
||||||
|
|
||||||
|
(See the documentation for type Package for the complete list of fields
|
||||||
|
and more detailed descriptions.)
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
Load(nil, "bytes", "unicode...")
|
||||||
|
|
||||||
|
returns four Package structs describing the standard library packages
|
||||||
|
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
|
||||||
|
can match multiple packages and that a package might be matched by
|
||||||
|
multiple patterns: in general it is not possible to determine which
|
||||||
|
packages correspond to which patterns.
|
||||||
|
|
||||||
|
Note that the list returned by Load contains only the packages matched
|
||||||
|
by the patterns. Their dependencies can be found by walking the import
|
||||||
|
graph using the Imports fields.
|
||||||
|
|
||||||
|
The Load function can be configured by passing a pointer to a Config as
|
||||||
|
the first argument. A nil Config is equivalent to the zero Config, which
|
||||||
|
causes Load to run in LoadFiles mode, collecting minimal information.
|
||||||
|
See the documentation for type Config for details.
|
||||||
|
|
||||||
|
As noted earlier, the Config.Mode controls the amount of detail
|
||||||
|
reported about the loaded packages. See the documentation for type LoadMode
|
||||||
|
for details.
|
||||||
|
|
||||||
|
Most tools should pass their command-line arguments (after any flags)
|
||||||
|
uninterpreted to the loader, so that the loader can interpret them
|
||||||
|
according to the conventions of the underlying build system.
|
||||||
|
See the Example function for typical usage.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package packages // import "golang.org/x/tools/go/packages"
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Motivation and design considerations
|
||||||
|
|
||||||
|
The new package's design solves problems addressed by two existing
|
||||||
|
packages: go/build, which locates and describes packages, and
|
||||||
|
golang.org/x/tools/go/loader, which loads, parses and type-checks them.
|
||||||
|
The go/build.Package structure encodes too much of the 'go build' way
|
||||||
|
of organizing projects, leaving us in need of a data type that describes a
|
||||||
|
package of Go source code independent of the underlying build system.
|
||||||
|
We wanted something that works equally well with go build and vgo, and
|
||||||
|
also other build systems such as Bazel and Blaze, making it possible to
|
||||||
|
construct analysis tools that work in all these environments.
|
||||||
|
Tools such as errcheck and staticcheck were essentially unavailable to
|
||||||
|
the Go community at Google, and some of Google's internal tools for Go
|
||||||
|
are unavailable externally.
|
||||||
|
This new package provides a uniform way to obtain package metadata by
|
||||||
|
querying each of these build systems, optionally supporting their
|
||||||
|
preferred command-line notations for packages, so that tools integrate
|
||||||
|
neatly with users' build environments. The Metadata query function
|
||||||
|
executes an external query tool appropriate to the current workspace.
|
||||||
|
|
||||||
|
Loading packages always returns the complete import graph "all the way down",
|
||||||
|
even if all you want is information about a single package, because the query
|
||||||
|
mechanisms of all the build systems we currently support ({go,vgo} list, and
|
||||||
|
blaze/bazel aspect-based query) cannot provide detailed information
|
||||||
|
about one package without visiting all its dependencies too, so there is
|
||||||
|
no additional asymptotic cost to providing transitive information.
|
||||||
|
(This property might not be true of a hypothetical 5th build system.)
|
||||||
|
|
||||||
|
In calls to TypeCheck, all initial packages, and any package that
|
||||||
|
transitively depends on one of them, must be loaded from source.
|
||||||
|
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
|
||||||
|
source; D may be loaded from export data, and E may not be loaded at all
|
||||||
|
(though it's possible that D's export data mentions it, so a
|
||||||
|
types.Package may be created for it and exposed.)
|
||||||
|
|
||||||
|
The old loader had a feature to suppress type-checking of function
|
||||||
|
bodies on a per-package basis, primarily intended to reduce the work of
|
||||||
|
obtaining type information for imported packages. Now that imports are
|
||||||
|
satisfied by export data, the optimization no longer seems necessary.
|
||||||
|
|
||||||
|
Despite some early attempts, the old loader did not exploit export data,
|
||||||
|
instead always using the equivalent of WholeProgram mode. This was due
|
||||||
|
to the complexity of mixing source and export data packages (now
|
||||||
|
resolved by the upward traversal mentioned above), and because export data
|
||||||
|
files were nearly always missing or stale. Now that 'go build' supports
|
||||||
|
caching, all the underlying build systems can guarantee to produce
|
||||||
|
export data in a reasonable (amortized) time.
|
||||||
|
|
||||||
|
Test "main" packages synthesized by the build system are now reported as
|
||||||
|
first-class packages, avoiding the need for clients (such as go/ssa) to
|
||||||
|
reinvent this generation logic.
|
||||||
|
|
||||||
|
One way in which go/packages is simpler than the old loader is in its
|
||||||
|
treatment of in-package tests. In-package tests are packages that
|
||||||
|
consist of all the files of the library under test, plus the test files.
|
||||||
|
The old loader constructed in-package tests by a two-phase process of
|
||||||
|
mutation called "augmentation": first it would construct and type check
|
||||||
|
all the ordinary library packages and type-check the packages that
|
||||||
|
depend on them; then it would add more (test) files to the package and
|
||||||
|
type-check again. This two-phase approach had four major problems:
|
||||||
|
1) in processing the tests, the loader modified the library package,
|
||||||
|
leaving no way for a client application to see both the test
|
||||||
|
package and the library package; one would mutate into the other.
|
||||||
|
2) because test files can declare additional methods on types defined in
|
||||||
|
the library portion of the package, the dispatch of method calls in
|
||||||
|
the library portion was affected by the presence of the test files.
|
||||||
|
This should have been a clue that the packages were logically
|
||||||
|
different.
|
||||||
|
3) this model of "augmentation" assumed at most one in-package test
|
||||||
|
per library package, which is true of projects using 'go build',
|
||||||
|
but not other build systems.
|
||||||
|
4) because of the two-phase nature of test processing, all packages that
|
||||||
|
import the library package had to be processed before augmentation,
|
||||||
|
forcing a "one-shot" API and preventing the client from calling Load
|
||||||
|
in several times in sequence as is now possible in WholeProgram mode.
|
||||||
|
(TypeCheck mode has a similar one-shot restriction for a different reason.)
|
||||||
|
|
||||||
|
Early drafts of this package supported "multi-shot" operation.
|
||||||
|
Although it allowed clients to make a sequence of calls (or concurrent
|
||||||
|
calls) to Load, building up the graph of Packages incrementally,
|
||||||
|
it was of marginal value: it complicated the API
|
||||||
|
(since it allowed some options to vary across calls but not others),
|
||||||
|
it complicated the implementation,
|
||||||
|
it cannot be made to work in Types mode, as explained above,
|
||||||
|
and it was less efficient than making one combined call (when this is possible).
|
||||||
|
Among the clients we have inspected, none made multiple calls to load
|
||||||
|
but could not be easily and satisfactorily modified to make only a single call.
|
||||||
|
However, applications changes may be required.
|
||||||
|
For example, the ssadump command loads the user-specified packages
|
||||||
|
and in addition the runtime package. It is tempting to simply append
|
||||||
|
"runtime" to the user-provided list, but that does not work if the user
|
||||||
|
specified an ad-hoc package such as [a.go b.go].
|
||||||
|
Instead, ssadump no longer requests the runtime package,
|
||||||
|
but seeks it among the dependencies of the user-specified packages,
|
||||||
|
and emits an error if it is not found.
|
||||||
|
|
||||||
|
Overlays: The Overlay field in the Config allows providing alternate contents
|
||||||
|
for Go source files, by providing a mapping from file path to contents.
|
||||||
|
go/packages will pull in new imports added in overlay files when go/packages
|
||||||
|
is run in LoadImports mode or greater.
|
||||||
|
Overlay support for the go list driver isn't complete yet: if the file doesn't
|
||||||
|
exist on disk, it will only be recognized in an overlay if it is a non-test file
|
||||||
|
and the package would be reported even without the overlay.
|
||||||
|
|
||||||
|
Questions & Tasks
|
||||||
|
|
||||||
|
- Add GOARCH/GOOS?
|
||||||
|
They are not portable concepts, but could be made portable.
|
||||||
|
Our goal has been to allow users to express themselves using the conventions
|
||||||
|
of the underlying build system: if the build system honors GOARCH
|
||||||
|
during a build and during a metadata query, then so should
|
||||||
|
applications built atop that query mechanism.
|
||||||
|
Conversely, if the target architecture of the build is determined by
|
||||||
|
command-line flags, the application can pass the relevant
|
||||||
|
flags through to the build system using a command such as:
|
||||||
|
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
|
||||||
|
However, this approach is low-level, unwieldy, and non-portable.
|
||||||
|
GOOS and GOARCH seem important enough to warrant a dedicated option.
|
||||||
|
|
||||||
|
- How should we handle partial failures such as a mixture of good and
|
||||||
|
malformed patterns, existing and non-existent packages, successful and
|
||||||
|
failed builds, import failures, import cycles, and so on, in a call to
|
||||||
|
Load?
|
||||||
|
|
||||||
|
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
|
||||||
|
|
||||||
|
- Handle (and test) various partial success cases, e.g.
|
||||||
|
a mixture of good packages and:
|
||||||
|
invalid patterns
|
||||||
|
nonexistent packages
|
||||||
|
empty packages
|
||||||
|
packages with malformed package or import declarations
|
||||||
|
unreadable files
|
||||||
|
import cycles
|
||||||
|
other parse errors
|
||||||
|
type errors
|
||||||
|
Make sure we record errors at the correct place in the graph.
|
||||||
|
|
||||||
|
- Missing packages among initial arguments are not reported.
|
||||||
|
Return bogus packages for them, like golist does.
|
||||||
|
|
||||||
|
- "undeclared name" errors (for example) are reported out of source file
|
||||||
|
order. I suspect this is due to the breadth-first resolution now used
|
||||||
|
by go/types. Is that a bug? Discuss with gri.
|
||||||
|
|
||||||
|
*/
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file enables an external tool to intercept package requests.
|
||||||
|
// If the tool is present then its results are used in preference to
|
||||||
|
// the go list command.
|
||||||
|
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The Driver Protocol
|
||||||
|
//
|
||||||
|
// The driver, given the inputs to a call to Load, returns metadata about the packages specified.
|
||||||
|
// This allows for different build systems to support go/packages by telling go/packages how the
|
||||||
|
// packages' source is organized.
|
||||||
|
// The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in
|
||||||
|
// the path as gopackagesdriver. It's given the inputs to load in its argv. See the package
|
||||||
|
// documentation in doc.go for the full description of the patterns that need to be supported.
|
||||||
|
// A driver receives as a JSON-serialized driverRequest struct in standard input and will
|
||||||
|
// produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output.
|
||||||
|
|
||||||
|
// driverRequest is used to provide the portion of Load's Config that is needed by a driver.
|
||||||
|
type driverRequest struct {
|
||||||
|
Mode LoadMode `json:"mode"`
|
||||||
|
// Env specifies the environment the underlying build system should be run in.
|
||||||
|
Env []string `json:"env"`
|
||||||
|
// BuildFlags are flags that should be passed to the underlying build system.
|
||||||
|
BuildFlags []string `json:"build_flags"`
|
||||||
|
// Tests specifies whether the patterns should also return test packages.
|
||||||
|
Tests bool `json:"tests"`
|
||||||
|
// Overlay maps file paths (relative to the driver's working directory) to the byte contents
|
||||||
|
// of overlay files.
|
||||||
|
Overlay map[string][]byte `json:"overlay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// findExternalDriver returns the file path of a tool that supplies
|
||||||
|
// the build system package structure, or "" if not found."
|
||||||
|
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
||||||
|
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
||||||
|
func findExternalDriver(cfg *Config) driver {
|
||||||
|
const toolPrefix = "GOPACKAGESDRIVER="
|
||||||
|
tool := ""
|
||||||
|
for _, env := range cfg.Env {
|
||||||
|
if val := strings.TrimPrefix(env, toolPrefix); val != env {
|
||||||
|
tool = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tool != "" && tool == "off" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tool == "" {
|
||||||
|
var err error
|
||||||
|
tool, err = exec.LookPath("gopackagesdriver")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func(cfg *Config, words ...string) (*driverResponse, error) {
|
||||||
|
req, err := json.Marshal(driverRequest{
|
||||||
|
Mode: cfg.Mode,
|
||||||
|
Env: cfg.Env,
|
||||||
|
BuildFlags: cfg.BuildFlags,
|
||||||
|
Tests: cfg.Tests,
|
||||||
|
Overlay: cfg.Overlay,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
cmd := exec.CommandContext(cfg.Context, tool, words...)
|
||||||
|
cmd.Dir = cfg.Dir
|
||||||
|
cmd.Env = cfg.Env
|
||||||
|
cmd.Stdin = bytes.NewReader(req)
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||||
|
}
|
||||||
|
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response driverResponse
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,568 @@
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processGolistOverlay provides rudimentary support for adding
|
||||||
|
// files that don't exist on disk to an overlay. The results can be
|
||||||
|
// sometimes incorrect.
|
||||||
|
// TODO(matloob): Handle unsupported cases, including the following:
|
||||||
|
// - determining the correct package to add given a new import path
|
||||||
|
func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
|
||||||
|
havePkgs := make(map[string]string) // importPath -> non-test package ID
|
||||||
|
needPkgsSet := make(map[string]bool)
|
||||||
|
modifiedPkgsSet := make(map[string]bool)
|
||||||
|
|
||||||
|
pkgOfDir := make(map[string][]*Package)
|
||||||
|
for _, pkg := range response.dr.Packages {
|
||||||
|
// This is an approximation of import path to id. This can be
|
||||||
|
// wrong for tests, vendored packages, and a number of other cases.
|
||||||
|
havePkgs[pkg.PkgPath] = pkg.ID
|
||||||
|
x := commonDir(pkg.GoFiles)
|
||||||
|
if x != "" {
|
||||||
|
pkgOfDir[x] = append(pkgOfDir[x], pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no new imports are added, it is safe to avoid loading any needPkgs.
|
||||||
|
// Otherwise, it's hard to tell which package is actually being loaded
|
||||||
|
// (due to vendoring) and whether any modified package will show up
|
||||||
|
// in the transitive set of dependencies (because new imports are added,
|
||||||
|
// potentially modifying the transitive set of dependencies).
|
||||||
|
var overlayAddsImports bool
|
||||||
|
|
||||||
|
// If both a package and its test package are created by the overlay, we
|
||||||
|
// need the real package first. Process all non-test files before test
|
||||||
|
// files, and make the whole process deterministic while we're at it.
|
||||||
|
var overlayFiles []string
|
||||||
|
for opath := range state.cfg.Overlay {
|
||||||
|
overlayFiles = append(overlayFiles, opath)
|
||||||
|
}
|
||||||
|
sort.Slice(overlayFiles, func(i, j int) bool {
|
||||||
|
iTest := strings.HasSuffix(overlayFiles[i], "_test.go")
|
||||||
|
jTest := strings.HasSuffix(overlayFiles[j], "_test.go")
|
||||||
|
if iTest != jTest {
|
||||||
|
return !iTest // non-tests are before tests.
|
||||||
|
}
|
||||||
|
return overlayFiles[i] < overlayFiles[j]
|
||||||
|
})
|
||||||
|
for _, opath := range overlayFiles {
|
||||||
|
contents := state.cfg.Overlay[opath]
|
||||||
|
base := filepath.Base(opath)
|
||||||
|
dir := filepath.Dir(opath)
|
||||||
|
var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant
|
||||||
|
var testVariantOf *Package // if opath is a test file, this is the package it is testing
|
||||||
|
var fileExists bool
|
||||||
|
isTestFile := strings.HasSuffix(opath, "_test.go")
|
||||||
|
pkgName, ok := extractPackageName(opath, contents)
|
||||||
|
if !ok {
|
||||||
|
// Don't bother adding a file that doesn't even have a parsable package statement
|
||||||
|
// to the overlay.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If all the overlay files belong to a different package, change the
|
||||||
|
// package name to that package.
|
||||||
|
maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir])
|
||||||
|
nextPackage:
|
||||||
|
for _, p := range response.dr.Packages {
|
||||||
|
if pkgName != p.Name && p.ID != "command-line-arguments" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, f := range p.GoFiles {
|
||||||
|
if !sameFile(filepath.Dir(f), dir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Make sure to capture information on the package's test variant, if needed.
|
||||||
|
if isTestFile && !hasTestFiles(p) {
|
||||||
|
// TODO(matloob): Are there packages other than the 'production' variant
|
||||||
|
// of a package that this can match? This shouldn't match the test main package
|
||||||
|
// because the file is generated in another directory.
|
||||||
|
testVariantOf = p
|
||||||
|
continue nextPackage
|
||||||
|
} else if !isTestFile && hasTestFiles(p) {
|
||||||
|
// We're examining a test variant, but the overlaid file is
|
||||||
|
// a non-test file. Because the overlay implementation
|
||||||
|
// (currently) only adds a file to one package, skip this
|
||||||
|
// package, so that we can add the file to the production
|
||||||
|
// variant of the package. (https://golang.org/issue/36857
|
||||||
|
// tracks handling overlays on both the production and test
|
||||||
|
// variant of a package).
|
||||||
|
continue nextPackage
|
||||||
|
}
|
||||||
|
if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
|
||||||
|
// We have already seen the production version of the
|
||||||
|
// for which p is a test variant.
|
||||||
|
if hasTestFiles(p) {
|
||||||
|
testVariantOf = pkg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkg = p
|
||||||
|
if filepath.Base(f) == base {
|
||||||
|
fileExists = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The overlay could have included an entirely new package or an
|
||||||
|
// ad-hoc package. An ad-hoc package is one that we have manually
|
||||||
|
// constructed from inadequate `go list` results for a file= query.
|
||||||
|
// It will have the ID command-line-arguments.
|
||||||
|
if pkg == nil || pkg.ID == "command-line-arguments" {
|
||||||
|
// Try to find the module or gopath dir the file is contained in.
|
||||||
|
// Then for modules, add the module opath to the beginning.
|
||||||
|
pkgPath, ok, err := state.getPkgPath(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var forTest string // only set for x tests
|
||||||
|
isXTest := strings.HasSuffix(pkgName, "_test")
|
||||||
|
if isXTest {
|
||||||
|
forTest = pkgPath
|
||||||
|
pkgPath += "_test"
|
||||||
|
}
|
||||||
|
id := pkgPath
|
||||||
|
if isTestFile {
|
||||||
|
if isXTest {
|
||||||
|
id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest)
|
||||||
|
} else {
|
||||||
|
id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pkg != nil {
|
||||||
|
// TODO(rstambler): We should change the package's path and ID
|
||||||
|
// here. The only issue is that this messes with the roots.
|
||||||
|
} else {
|
||||||
|
// Try to reclaim a package with the same ID, if it exists in the response.
|
||||||
|
for _, p := range response.dr.Packages {
|
||||||
|
if reclaimPackage(p, id, opath, contents) {
|
||||||
|
pkg = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, create a new package.
|
||||||
|
if pkg == nil {
|
||||||
|
pkg = &Package{
|
||||||
|
PkgPath: pkgPath,
|
||||||
|
ID: id,
|
||||||
|
Name: pkgName,
|
||||||
|
Imports: make(map[string]*Package),
|
||||||
|
}
|
||||||
|
response.addPackage(pkg)
|
||||||
|
havePkgs[pkg.PkgPath] = id
|
||||||
|
// Add the production package's sources for a test variant.
|
||||||
|
if isTestFile && !isXTest && testVariantOf != nil {
|
||||||
|
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
|
||||||
|
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
|
||||||
|
// Add the package under test and its imports to the test variant.
|
||||||
|
pkg.forTest = testVariantOf.PkgPath
|
||||||
|
for k, v := range testVariantOf.Imports {
|
||||||
|
pkg.Imports[k] = &Package{ID: v.ID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isXTest {
|
||||||
|
pkg.forTest = forTest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fileExists {
|
||||||
|
pkg.GoFiles = append(pkg.GoFiles, opath)
|
||||||
|
// TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior
|
||||||
|
// if the file will be ignored due to its build tags.
|
||||||
|
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
|
||||||
|
modifiedPkgsSet[pkg.ID] = true
|
||||||
|
}
|
||||||
|
imports, err := extractImports(opath, contents)
|
||||||
|
if err != nil {
|
||||||
|
// Let the parser or type checker report errors later.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, imp := range imports {
|
||||||
|
// TODO(rstambler): If the package is an x test and the import has
|
||||||
|
// a test variant, make sure to replace it.
|
||||||
|
if _, found := pkg.Imports[imp]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
overlayAddsImports = true
|
||||||
|
id, ok := havePkgs[imp]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
id, err = state.resolveImport(dir, imp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkg.Imports[imp] = &Package{ID: id}
|
||||||
|
// Add dependencies to the non-test variant version of this package as well.
|
||||||
|
if testVariantOf != nil {
|
||||||
|
testVariantOf.Imports[imp] = &Package{ID: id}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toPkgPath guesses the package path given the id.
|
||||||
|
toPkgPath := func(sourceDir, id string) (string, error) {
|
||||||
|
if i := strings.IndexByte(id, ' '); i >= 0 {
|
||||||
|
return state.resolveImport(sourceDir, id[:i])
|
||||||
|
}
|
||||||
|
return state.resolveImport(sourceDir, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that new packages have been created, do another pass to determine
|
||||||
|
// the new set of missing packages.
|
||||||
|
for _, pkg := range response.dr.Packages {
|
||||||
|
for _, imp := range pkg.Imports {
|
||||||
|
if len(pkg.GoFiles) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
|
||||||
|
}
|
||||||
|
pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if _, ok := havePkgs[pkgPath]; !ok {
|
||||||
|
needPkgsSet[pkgPath] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if overlayAddsImports {
|
||||||
|
needPkgs = make([]string, 0, len(needPkgsSet))
|
||||||
|
for pkg := range needPkgsSet {
|
||||||
|
needPkgs = append(needPkgs, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modifiedPkgs = make([]string, 0, len(modifiedPkgsSet))
|
||||||
|
for pkg := range modifiedPkgsSet {
|
||||||
|
modifiedPkgs = append(modifiedPkgs, pkg)
|
||||||
|
}
|
||||||
|
return modifiedPkgs, needPkgs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveImport finds the ID of a package given its import path.
|
||||||
|
// In particular, it will find the right vendored copy when in GOPATH mode.
|
||||||
|
func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
|
||||||
|
env, err := state.getEnv()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if env["GOMOD"] != "" {
|
||||||
|
return importPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDir := sourceDir
|
||||||
|
for {
|
||||||
|
vendorDir := filepath.Join(searchDir, "vendor")
|
||||||
|
exists, ok := state.vendorDirs[vendorDir]
|
||||||
|
if !ok {
|
||||||
|
info, err := os.Stat(vendorDir)
|
||||||
|
exists = err == nil && info.IsDir()
|
||||||
|
state.vendorDirs[vendorDir] = exists
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
vendoredPath := filepath.Join(vendorDir, importPath)
|
||||||
|
if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
|
||||||
|
// We should probably check for .go files here, but shame on anyone who fools us.
|
||||||
|
path, ok, err := state.getPkgPath(vendoredPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We know we've hit the top of the filesystem when we Dir / and get /,
|
||||||
|
// or C:\ and get C:\, etc.
|
||||||
|
next := filepath.Dir(searchDir)
|
||||||
|
if next == searchDir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
searchDir = next
|
||||||
|
}
|
||||||
|
return importPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasTestFiles(p *Package) bool {
|
||||||
|
for _, f := range p.GoFiles {
|
||||||
|
if strings.HasSuffix(f, "_test.go") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// determineRootDirs returns a mapping from absolute directories that could
|
||||||
|
// contain code to their corresponding import path prefixes.
|
||||||
|
func (state *golistState) determineRootDirs() (map[string]string, error) {
|
||||||
|
env, err := state.getEnv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if env["GOMOD"] != "" {
|
||||||
|
state.rootsOnce.Do(func() {
|
||||||
|
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.rootsOnce.Do(func() {
|
||||||
|
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return state.rootDirs, state.rootDirsError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
|
||||||
|
// List all of the modules--the first will be the directory for the main
|
||||||
|
// module. Any replaced modules will also need to be treated as roots.
|
||||||
|
// Editing files in the module cache isn't a great idea, so we don't
|
||||||
|
// plan to ever support that.
|
||||||
|
out, err := state.invokeGo("list", "-m", "-json", "all")
|
||||||
|
if err != nil {
|
||||||
|
// 'go list all' will fail if we're outside of a module and
|
||||||
|
// GO111MODULE=on. Try falling back without 'all'.
|
||||||
|
var innerErr error
|
||||||
|
out, innerErr = state.invokeGo("list", "-m", "-json")
|
||||||
|
if innerErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roots := map[string]string{}
|
||||||
|
modules := map[string]string{}
|
||||||
|
var i int
|
||||||
|
for dec := json.NewDecoder(out); dec.More(); {
|
||||||
|
mod := new(gocommand.ModuleJSON)
|
||||||
|
if err := dec.Decode(mod); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if mod.Dir != "" && mod.Path != "" {
|
||||||
|
// This is a valid module; add it to the map.
|
||||||
|
absDir, err := filepath.Abs(mod.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
modules[absDir] = mod.Path
|
||||||
|
// The first result is the main module.
|
||||||
|
if i == 0 || mod.Replace != nil && mod.Replace.Path != "" {
|
||||||
|
roots[absDir] = mod.Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return roots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
|
||||||
|
m := map[string]string{}
|
||||||
|
for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
|
||||||
|
absDir, err := filepath.Abs(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[filepath.Join(absDir, "src")] = ""
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractImports(filename string, contents []byte) ([]string, error) {
|
||||||
|
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset?
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res []string
|
||||||
|
for _, imp := range f.Imports {
|
||||||
|
quotedPath := imp.Path.Value
|
||||||
|
path, err := strconv.Unquote(quotedPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, path)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reclaimPackage attempts to reuse a package that failed to load in an overlay.
|
||||||
|
//
|
||||||
|
// If the package has errors and has no Name, GoFiles, or Imports,
|
||||||
|
// then it's possible that it doesn't yet exist on disk.
|
||||||
|
func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool {
|
||||||
|
// TODO(rstambler): Check the message of the actual error?
|
||||||
|
// It differs between $GOPATH and module mode.
|
||||||
|
if pkg.ID != id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(pkg.Errors) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pkg.Name != "" || pkg.ExportFile != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(pkg.Imports) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pkgName, ok := extractPackageName(filename, contents)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pkg.Name = pkgName
|
||||||
|
pkg.Errors = nil
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPackageName(filename string, contents []byte) (string, bool) {
|
||||||
|
// TODO(rstambler): Check the message of the actual error?
|
||||||
|
// It differs between $GOPATH and module mode.
|
||||||
|
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return f.Name.Name, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonDir(a []string) string {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
x := append([]string{}, a...)
|
||||||
|
for _, f := range x {
|
||||||
|
seen[filepath.Dir(f)] = true
|
||||||
|
}
|
||||||
|
if len(seen) > 1 {
|
||||||
|
log.Fatalf("commonDir saw %v for %v", seen, x)
|
||||||
|
}
|
||||||
|
for k := range seen {
|
||||||
|
// len(seen) == 1
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
return "" // no files
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is possible that the files in the disk directory dir have a different package
|
||||||
|
// name from newName, which is deduced from the overlays. If they all have a different
|
||||||
|
// package name, and they all have the same package name, then that name becomes
|
||||||
|
// the package name.
|
||||||
|
// It returns true if it changes the package name, false otherwise.
|
||||||
|
func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) {
|
||||||
|
names := make(map[string]int)
|
||||||
|
for _, p := range pkgsOfDir {
|
||||||
|
names[p.Name]++
|
||||||
|
}
|
||||||
|
if len(names) != 1 {
|
||||||
|
// some files are in different packages
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var oldName string
|
||||||
|
for k := range names {
|
||||||
|
oldName = k
|
||||||
|
}
|
||||||
|
if newName == oldName {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We might have a case where all of the package names in the directory are
|
||||||
|
// the same, but the overlay file is for an x test, which belongs to its
|
||||||
|
// own package. If the x test does not yet exist on disk, we may not yet
|
||||||
|
// have its package name on disk, but we should not rename the packages.
|
||||||
|
//
|
||||||
|
// We use a heuristic to determine if this file belongs to an x test:
|
||||||
|
// The test file should have a package name whose package name has a _test
|
||||||
|
// suffix or looks like "newName_test".
|
||||||
|
maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test")
|
||||||
|
if isTestFile && maybeXTest {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, p := range pkgsOfDir {
|
||||||
|
p.Name = newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is copy-pasted from
|
||||||
|
// https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360.
|
||||||
|
// It should be deleted when we remove support for overlays from go/packages.
|
||||||
|
//
|
||||||
|
// NOTE: This does not handle any ./... or ./ style queries, as this function
|
||||||
|
// doesn't know the working directory.
|
||||||
|
//
|
||||||
|
// matchPattern(pattern)(name) reports whether
|
||||||
|
// name matches pattern. Pattern is a limited glob
|
||||||
|
// pattern in which '...' means 'any string' and there
|
||||||
|
// is no other special syntax.
|
||||||
|
// Unfortunately, there are two special cases. Quoting "go help packages":
|
||||||
|
//
|
||||||
|
// First, /... at the end of the pattern can match an empty string,
|
||||||
|
// so that net/... matches both net and packages in its subdirectories, like net/http.
|
||||||
|
// Second, any slash-separated pattern element containing a wildcard never
|
||||||
|
// participates in a match of the "vendor" element in the path of a vendored
|
||||||
|
// package, so that ./... does not match packages in subdirectories of
|
||||||
|
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
|
||||||
|
// Note, however, that a directory named vendor that itself contains code
|
||||||
|
// is not a vendored package: cmd/vendor would be a command named vendor,
|
||||||
|
// and the pattern cmd/... matches it.
|
||||||
|
func matchPattern(pattern string) func(name string) bool {
|
||||||
|
// Convert pattern to regular expression.
|
||||||
|
// The strategy for the trailing /... is to nest it in an explicit ? expression.
|
||||||
|
// The strategy for the vendor exclusion is to change the unmatchable
|
||||||
|
// vendor strings to a disallowed code point (vendorChar) and to use
|
||||||
|
// "(anything but that codepoint)*" as the implementation of the ... wildcard.
|
||||||
|
// This is a bit complicated but the obvious alternative,
|
||||||
|
// namely a hand-written search like in most shell glob matchers,
|
||||||
|
// is too easy to make accidentally exponential.
|
||||||
|
// Using package regexp guarantees linear-time matching.
|
||||||
|
|
||||||
|
const vendorChar = "\x00"
|
||||||
|
|
||||||
|
if strings.Contains(pattern, vendorChar) {
|
||||||
|
return func(name string) bool { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.QuoteMeta(pattern)
|
||||||
|
re = replaceVendor(re, vendorChar)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
|
||||||
|
re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
|
||||||
|
case re == vendorChar+`/\.\.\.`:
|
||||||
|
re = `(/vendor|/` + vendorChar + `/\.\.\.)`
|
||||||
|
case strings.HasSuffix(re, `/\.\.\.`):
|
||||||
|
re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
|
||||||
|
}
|
||||||
|
re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
|
||||||
|
|
||||||
|
reg := regexp.MustCompile(`^` + re + `$`)
|
||||||
|
|
||||||
|
return func(name string) bool {
|
||||||
|
if strings.Contains(name, vendorChar) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return reg.MatchString(replaceVendor(name, vendorChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceVendor returns the result of replacing
|
||||||
|
// non-trailing vendor path elements in x with repl.
|
||||||
|
func replaceVendor(x, repl string) string {
|
||||||
|
if !strings.Contains(x, "vendor") {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
elem := strings.Split(x, "/")
|
||||||
|
for i := 0; i < len(elem)-1; i++ {
|
||||||
|
if elem[i] == "vendor" {
|
||||||
|
elem[i] = repl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(elem, "/")
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visit visits all the packages in the import graph whose roots are
|
||||||
|
// pkgs, calling the optional pre function the first time each package
|
||||||
|
// is encountered (preorder), and the optional post function after a
|
||||||
|
// package's dependencies have been visited (postorder).
|
||||||
|
// The boolean result of pre(pkg) determines whether
|
||||||
|
// the imports of package pkg are visited.
|
||||||
|
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
|
||||||
|
seen := make(map[*Package]bool)
|
||||||
|
var visit func(*Package)
|
||||||
|
visit = func(pkg *Package) {
|
||||||
|
if !seen[pkg] {
|
||||||
|
seen[pkg] = true
|
||||||
|
|
||||||
|
if pre == nil || pre(pkg) {
|
||||||
|
paths := make([]string, 0, len(pkg.Imports))
|
||||||
|
for path := range pkg.Imports {
|
||||||
|
paths = append(paths, path)
|
||||||
|
}
|
||||||
|
sort.Strings(paths) // Imports is a map, this makes visit stable
|
||||||
|
for _, path := range paths {
|
||||||
|
visit(pkg.Imports[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if post != nil {
|
||||||
|
post(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
visit(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintErrors prints to os.Stderr the accumulated errors of all
|
||||||
|
// packages in the import graph rooted at pkgs, dependencies first.
|
||||||
|
// PrintErrors returns the number of errors printed.
|
||||||
|
func PrintErrors(pkgs []*Package) int {
|
||||||
|
var n int
|
||||||
|
Visit(pkgs, nil, func(pkg *Package) {
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return n
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package core provides support for event based telemetry.
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event holds the information about an event of note that ocurred.
|
||||||
|
type Event struct {
|
||||||
|
at time.Time
|
||||||
|
|
||||||
|
// As events are often on the stack, storing the first few labels directly
|
||||||
|
// in the event can avoid an allocation at all for the very common cases of
|
||||||
|
// simple events.
|
||||||
|
// The length needs to be large enough to cope with the majority of events
|
||||||
|
// but no so large as to cause undue stack pressure.
|
||||||
|
// A log message with two values will use 3 labels (one for each value and
|
||||||
|
// one for the message itself).
|
||||||
|
|
||||||
|
static [3]label.Label // inline storage for the first few labels
|
||||||
|
dynamic []label.Label // dynamically sized storage for remaining labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventLabelMap implements label.Map for a the labels of an Event.
|
||||||
|
type eventLabelMap struct {
|
||||||
|
event Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ev Event) At() time.Time { return ev.at }
|
||||||
|
|
||||||
|
func (ev Event) Format(f fmt.State, r rune) {
|
||||||
|
if !ev.at.IsZero() {
|
||||||
|
fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 "))
|
||||||
|
}
|
||||||
|
for index := 0; ev.Valid(index); index++ {
|
||||||
|
if l := ev.Label(index); l.Valid() {
|
||||||
|
fmt.Fprintf(f, "\n\t%v", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ev Event) Valid(index int) bool {
|
||||||
|
return index >= 0 && index < len(ev.static)+len(ev.dynamic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ev Event) Label(index int) label.Label {
|
||||||
|
if index < len(ev.static) {
|
||||||
|
return ev.static[index]
|
||||||
|
}
|
||||||
|
return ev.dynamic[index-len(ev.static)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ev Event) Find(key label.Key) label.Label {
|
||||||
|
for _, l := range ev.static {
|
||||||
|
if l.Key() == key {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range ev.dynamic {
|
||||||
|
if l.Key() == key {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return label.Label{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeEvent(static [3]label.Label, labels []label.Label) Event {
|
||||||
|
return Event{
|
||||||
|
static: static,
|
||||||
|
dynamic: labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneEvent event returns a copy of the event with the time adjusted to at.
|
||||||
|
func CloneEvent(ev Event, at time.Time) Event {
|
||||||
|
ev.at = at
|
||||||
|
return ev
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exporter is a function that handles events.
|
||||||
|
// It may return a modified context and event.
|
||||||
|
type Exporter func(context.Context, Event, label.Map) context.Context
|
||||||
|
|
||||||
|
var (
|
||||||
|
exporter unsafe.Pointer
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetExporter sets the global exporter function that handles all events.
|
||||||
|
// The exporter is called synchronously from the event call site, so it should
|
||||||
|
// return quickly so as not to hold up user code.
|
||||||
|
func SetExporter(e Exporter) {
|
||||||
|
p := unsafe.Pointer(&e)
|
||||||
|
if e == nil {
|
||||||
|
// &e is always valid, and so p is always valid, but for the early abort
|
||||||
|
// of ProcessEvent to be efficient it needs to make the nil check on the
|
||||||
|
// pointer without having to dereference it, so we make the nil function
|
||||||
|
// also a nil pointer
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
atomic.StorePointer(&exporter, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliver is called to deliver an event to the supplied exporter.
|
||||||
|
// it will fill in the time.
|
||||||
|
func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context {
|
||||||
|
// add the current time to the event
|
||||||
|
ev.at = time.Now()
|
||||||
|
// hand the event off to the current exporter
|
||||||
|
return exporter(ctx, ev, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export is called to deliver an event to the global exporter if set.
|
||||||
|
func Export(ctx context.Context, ev Event) context.Context {
|
||||||
|
// get the global exporter and abort early if there is not one
|
||||||
|
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
|
||||||
|
if exporterPtr == nil {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
return deliver(ctx, *exporterPtr, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPair is called to deliver a start event to the supplied exporter.
|
||||||
|
// It also returns a function that will deliver the end event to the same
|
||||||
|
// exporter.
|
||||||
|
// It will fill in the time.
|
||||||
|
func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) {
|
||||||
|
// get the global exporter and abort early if there is not one
|
||||||
|
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
|
||||||
|
if exporterPtr == nil {
|
||||||
|
return ctx, func() {}
|
||||||
|
}
|
||||||
|
ctx = deliver(ctx, *exporterPtr, begin)
|
||||||
|
return ctx, func() { deliver(ctx, *exporterPtr, end) }
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event/keys"
|
||||||
|
"golang.org/x/tools/internal/event/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log1 takes a message and one label delivers a log event to the exporter.
|
||||||
|
// It is a customized version of Print that is faster and does no allocation.
|
||||||
|
func Log1(ctx context.Context, message string, t1 label.Label) {
|
||||||
|
Export(ctx, MakeEvent([3]label.Label{
|
||||||
|
keys.Msg.Of(message),
|
||||||
|
t1,
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log2 takes a message and two labels and delivers a log event to the exporter.
|
||||||
|
// It is a customized version of Print that is faster and does no allocation.
|
||||||
|
func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) {
|
||||||
|
Export(ctx, MakeEvent([3]label.Label{
|
||||||
|
keys.Msg.Of(message),
|
||||||
|
t1,
|
||||||
|
t2,
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metric1 sends a label event to the exporter with the supplied labels.
|
||||||
|
func Metric1(ctx context.Context, t1 label.Label) context.Context {
|
||||||
|
return Export(ctx, MakeEvent([3]label.Label{
|
||||||
|
keys.Metric.New(),
|
||||||
|
t1,
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metric2 sends a label event to the exporter with the supplied labels.
|
||||||
|
func Metric2(ctx context.Context, t1, t2 label.Label) context.Context {
|
||||||
|
return Export(ctx, MakeEvent([3]label.Label{
|
||||||
|
keys.Metric.New(),
|
||||||
|
t1,
|
||||||
|
t2,
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start1 sends a span start event with the supplied label list to the exporter.
|
||||||
|
// It also returns a function that will end the span, which should normally be
|
||||||
|
// deferred.
|
||||||
|
func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) {
|
||||||
|
return ExportPair(ctx,
|
||||||
|
MakeEvent([3]label.Label{
|
||||||
|
keys.Start.Of(name),
|
||||||
|
t1,
|
||||||
|
}, nil),
|
||||||
|
MakeEvent([3]label.Label{
|
||||||
|
keys.End.New(),
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start2 sends a span start event with the supplied label list to the exporter.
|
||||||
|
// It also returns a function that will end the span, which should normally be
|
||||||
|
// deferred.
|
||||||
|
func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) {
|
||||||
|
return ExportPair(ctx,
|
||||||
|
MakeEvent([3]label.Label{
|
||||||
|
keys.Start.Of(name),
|
||||||
|
t1,
|
||||||
|
t2,
|
||||||
|
}, nil),
|
||||||
|
MakeEvent([3]label.Label{
|
||||||
|
keys.End.New(),
|
||||||
|
}, nil))
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package event provides a set of packages that cover the main
|
||||||
|
// concepts of telemetry in an implementation agnostic way.
|
||||||
|
package event
|
|
@ -0,0 +1,127 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event/core"
|
||||||
|
"golang.org/x/tools/internal/event/keys"
|
||||||
|
"golang.org/x/tools/internal/event/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exporter is a function that handles events.
|
||||||
|
// It may return a modified context and event.
|
||||||
|
type Exporter func(context.Context, core.Event, label.Map) context.Context
|
||||||
|
|
||||||
|
// SetExporter sets the global exporter function that handles all events.
|
||||||
|
// The exporter is called synchronously from the event call site, so it should
|
||||||
|
// return quickly so as not to hold up user code.
|
||||||
|
func SetExporter(e Exporter) {
|
||||||
|
core.SetExporter(core.Exporter(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log takes a message and a label list and combines them into a single event
|
||||||
|
// before delivering them to the exporter.
|
||||||
|
func Log(ctx context.Context, message string, labels ...label.Label) {
|
||||||
|
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||||
|
keys.Msg.Of(message),
|
||||||
|
}, labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLog returns true if the event was built by the Log function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsLog(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error takes a message and a label list and combines them into a single event
|
||||||
|
// before delivering them to the exporter. It captures the error in the
|
||||||
|
// delivered event.
|
||||||
|
func Error(ctx context.Context, message string, err error, labels ...label.Label) {
|
||||||
|
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||||
|
keys.Msg.Of(message),
|
||||||
|
keys.Err.Of(err),
|
||||||
|
}, labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsError returns true if the event was built by the Error function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsError(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Msg &&
|
||||||
|
ev.Label(1).Key() == keys.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metric sends a label event to the exporter with the supplied labels.
|
||||||
|
func Metric(ctx context.Context, labels ...label.Label) {
|
||||||
|
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||||
|
keys.Metric.New(),
|
||||||
|
}, labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMetric returns true if the event was built by the Metric function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsMetric(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Metric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label sends a label event to the exporter with the supplied labels.
|
||||||
|
func Label(ctx context.Context, labels ...label.Label) context.Context {
|
||||||
|
return core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||||
|
keys.Label.New(),
|
||||||
|
}, labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLabel returns true if the event was built by the Label function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsLabel(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start sends a span start event with the supplied label list to the exporter.
|
||||||
|
// It also returns a function that will end the span, which should normally be
|
||||||
|
// deferred.
|
||||||
|
func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) {
|
||||||
|
return core.ExportPair(ctx,
|
||||||
|
core.MakeEvent([3]label.Label{
|
||||||
|
keys.Start.Of(name),
|
||||||
|
}, labels),
|
||||||
|
core.MakeEvent([3]label.Label{
|
||||||
|
keys.End.New(),
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStart returns true if the event was built by the Start function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsStart(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Start
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnd returns true if the event was built by the End function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsEnd(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.End
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach returns a context without an associated span.
|
||||||
|
// This allows the creation of spans that are not children of the current span.
|
||||||
|
func Detach(ctx context.Context) context.Context {
|
||||||
|
return core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||||
|
keys.Detach.New(),
|
||||||
|
}, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDetach returns true if the event was built by the Detach function.
|
||||||
|
// It is intended to be used in exporters to identify the semantics of the
|
||||||
|
// event when deciding what to do with it.
|
||||||
|
func IsDetach(ev core.Event) bool {
|
||||||
|
return ev.Label(0).Key() == keys.Detach
|
||||||
|
}
|
|
@ -0,0 +1,564 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value represents a key for untyped values.
|
||||||
|
type Value struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Key for untyped values.
|
||||||
|
func New(name, description string) *Value {
|
||||||
|
return &Value{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Value) Name() string { return k.name }
|
||||||
|
func (k *Value) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Value) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
fmt.Fprint(w, k.From(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Value) Get(lm label.Map) interface{} {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() }
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) }
|
||||||
|
|
||||||
|
// Tag represents a key for tagging labels that have no value.
|
||||||
|
// These are used when the existence of the label is the entire information it
|
||||||
|
// carries, such as marking events to be of a specific kind, or from a specific
|
||||||
|
// package.
|
||||||
|
type Tag struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTag creates a new Key for tagging labels.
|
||||||
|
func NewTag(name, description string) *Tag {
|
||||||
|
return &Tag{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Tag) Name() string { return k.name }
|
||||||
|
func (k *Tag) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {}
|
||||||
|
|
||||||
|
// New creates a new Label with this key.
|
||||||
|
func (k *Tag) New() label.Label { return label.OfValue(k, nil) }
|
||||||
|
|
||||||
|
// Int represents a key
|
||||||
|
type Int struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt creates a new Key for int values.
|
||||||
|
func NewInt(name, description string) *Int {
|
||||||
|
return &Int{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Int) Name() string { return k.name }
|
||||||
|
func (k *Int) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Int) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Int) Get(lm label.Map) int {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Int) From(t label.Label) int { return int(t.Unpack64()) }
|
||||||
|
|
||||||
|
// Int8 represents a key
|
||||||
|
type Int8 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt8 creates a new Key for int8 values.
|
||||||
|
func NewInt8(name, description string) *Int8 {
|
||||||
|
return &Int8{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Int8) Name() string { return k.name }
|
||||||
|
func (k *Int8) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Int8) Get(lm label.Map) int8 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) }
|
||||||
|
|
||||||
|
// Int16 represents a key
|
||||||
|
type Int16 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt16 creates a new Key for int16 values.
|
||||||
|
func NewInt16(name, description string) *Int16 {
|
||||||
|
return &Int16{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Int16) Name() string { return k.name }
|
||||||
|
func (k *Int16) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Int16) Get(lm label.Map) int16 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) }
|
||||||
|
|
||||||
|
// Int32 represents a key
|
||||||
|
type Int32 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt32 creates a new Key for int32 values.
|
||||||
|
func NewInt32(name, description string) *Int32 {
|
||||||
|
return &Int32{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Int32) Name() string { return k.name }
|
||||||
|
func (k *Int32) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Int32) Get(lm label.Map) int32 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) }
|
||||||
|
|
||||||
|
// Int64 represents a key
|
||||||
|
type Int64 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64 creates a new Key for int64 values.
|
||||||
|
func NewInt64(name, description string) *Int64 {
|
||||||
|
return &Int64{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Int64) Name() string { return k.name }
|
||||||
|
func (k *Int64) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendInt(buf, k.From(l), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Int64) Get(lm label.Map) int64 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) }
|
||||||
|
|
||||||
|
// UInt represents a key
|
||||||
|
type UInt struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUInt creates a new Key for uint values.
|
||||||
|
func NewUInt(name, description string) *UInt {
|
||||||
|
return &UInt{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *UInt) Name() string { return k.name }
|
||||||
|
func (k *UInt) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *UInt) Get(lm label.Map) uint {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) }
|
||||||
|
|
||||||
|
// UInt8 represents a key
|
||||||
|
type UInt8 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUInt8 creates a new Key for uint8 values.
|
||||||
|
func NewUInt8(name, description string) *UInt8 {
|
||||||
|
return &UInt8{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *UInt8) Name() string { return k.name }
|
||||||
|
func (k *UInt8) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *UInt8) Get(lm label.Map) uint8 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) }
|
||||||
|
|
||||||
|
// UInt16 represents a key
|
||||||
|
type UInt16 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUInt16 creates a new Key for uint16 values.
|
||||||
|
func NewUInt16(name, description string) *UInt16 {
|
||||||
|
return &UInt16{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *UInt16) Name() string { return k.name }
|
||||||
|
func (k *UInt16) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *UInt16) Get(lm label.Map) uint16 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) }
|
||||||
|
|
||||||
|
// UInt32 represents a key
|
||||||
|
type UInt32 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUInt32 creates a new Key for uint32 values.
|
||||||
|
func NewUInt32(name, description string) *UInt32 {
|
||||||
|
return &UInt32{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *UInt32) Name() string { return k.name }
|
||||||
|
func (k *UInt32) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *UInt32) Get(lm label.Map) uint32 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) }
|
||||||
|
|
||||||
|
// UInt64 represents a key
|
||||||
|
type UInt64 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUInt64 creates a new Key for uint64 values.
|
||||||
|
func NewUInt64(name, description string) *UInt64 {
|
||||||
|
return &UInt64{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *UInt64) Name() string { return k.name }
|
||||||
|
func (k *UInt64) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendUint(buf, k.From(l), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *UInt64) Get(lm label.Map) uint64 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() }
|
||||||
|
|
||||||
|
// Float32 represents a key
|
||||||
|
type Float32 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat32 creates a new Key for float32 values.
|
||||||
|
func NewFloat32(name, description string) *Float32 {
|
||||||
|
return &Float32{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Float32) Name() string { return k.name }
|
||||||
|
func (k *Float32) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Float32) Of(v float32) label.Label {
|
||||||
|
return label.Of64(k, uint64(math.Float32bits(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Float32) Get(lm label.Map) float32 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Float32) From(t label.Label) float32 {
|
||||||
|
return math.Float32frombits(uint32(t.Unpack64()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 represents a key
|
||||||
|
type Float64 struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64 creates a new Key for int64 values.
|
||||||
|
func NewFloat64(name, description string) *Float64 {
|
||||||
|
return &Float64{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Float64) Name() string { return k.name }
|
||||||
|
func (k *Float64) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Float64) Of(v float64) label.Label {
|
||||||
|
return label.Of64(k, math.Float64bits(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Float64) Get(lm label.Map) float64 {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Float64) From(t label.Label) float64 {
|
||||||
|
return math.Float64frombits(t.Unpack64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// String represents a key
|
||||||
|
type String struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewString creates a new Key for int64 values.
|
||||||
|
func NewString(name, description string) *String {
|
||||||
|
return &String{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *String) Name() string { return k.name }
|
||||||
|
func (k *String) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *String) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendQuote(buf, k.From(l)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *String) Of(v string) label.Label { return label.OfString(k, v) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *String) Get(lm label.Map) string {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *String) From(t label.Label) string { return t.UnpackString() }
|
||||||
|
|
||||||
|
// Boolean represents a key
|
||||||
|
type Boolean struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBoolean creates a new Key for bool values.
|
||||||
|
func NewBoolean(name, description string) *Boolean {
|
||||||
|
return &Boolean{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Boolean) Name() string { return k.name }
|
||||||
|
func (k *Boolean) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
w.Write(strconv.AppendBool(buf, k.From(l)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Boolean) Of(v bool) label.Label {
|
||||||
|
if v {
|
||||||
|
return label.Of64(k, 1)
|
||||||
|
}
|
||||||
|
return label.Of64(k, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Boolean) Get(lm label.Map) bool {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 }
|
||||||
|
|
||||||
|
// Error represents a key
|
||||||
|
type Error struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError creates a new Key for int64 values.
|
||||||
|
func NewError(name, description string) *Error {
|
||||||
|
return &Error{name: name, description: description}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Error) Name() string { return k.name }
|
||||||
|
func (k *Error) Description() string { return k.description }
|
||||||
|
|
||||||
|
func (k *Error) Format(w io.Writer, buf []byte, l label.Label) {
|
||||||
|
io.WriteString(w, k.From(l).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of creates a new Label with this key and the supplied value.
|
||||||
|
func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) }
|
||||||
|
|
||||||
|
// Get can be used to get a label for the key from a label.Map.
|
||||||
|
func (k *Error) Get(lm label.Map) error {
|
||||||
|
if t := lm.Find(k); t.Valid() {
|
||||||
|
return k.From(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// From can be used to get a value from a Label.
|
||||||
|
func (k *Error) From(t label.Label) error {
|
||||||
|
err, _ := t.UnpackValue().(error)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package keys
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Msg is a key used to add message strings to label lists.
|
||||||
|
Msg = NewString("message", "a readable message")
|
||||||
|
// Label is a key used to indicate an event adds labels to the context.
|
||||||
|
Label = NewTag("label", "a label context marker")
|
||||||
|
// Start is used for things like traces that have a name.
|
||||||
|
Start = NewString("start", "span start")
|
||||||
|
// Metric is a key used to indicate an event records metrics.
|
||||||
|
End = NewTag("end", "a span end marker")
|
||||||
|
// Metric is a key used to indicate an event records metrics.
|
||||||
|
Detach = NewTag("detach", "a span detach marker")
|
||||||
|
// Err is a key used to add error values to label lists.
|
||||||
|
Err = NewError("error", "an error that occurred")
|
||||||
|
// Metric is a key used to indicate an event records metrics.
|
||||||
|
Metric = NewTag("metric", "a metric event marker")
|
||||||
|
)
|
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package label
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key is used as the identity of a Label.
|
||||||
|
// Keys are intended to be compared by pointer only, the name should be unique
|
||||||
|
// for communicating with external systems, but it is not required or enforced.
|
||||||
|
type Key interface {
|
||||||
|
// Name returns the key name.
|
||||||
|
Name() string
|
||||||
|
// Description returns a string that can be used to describe the value.
|
||||||
|
Description() string
|
||||||
|
|
||||||
|
// Format is used in formatting to append the value of the label to the
|
||||||
|
// supplied buffer.
|
||||||
|
// The formatter may use the supplied buf as a scratch area to avoid
|
||||||
|
// allocations.
|
||||||
|
Format(w io.Writer, buf []byte, l Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label holds a key and value pair.
|
||||||
|
// It is normally used when passing around lists of labels.
|
||||||
|
type Label struct {
|
||||||
|
key Key
|
||||||
|
packed uint64
|
||||||
|
untyped interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map is the interface to a collection of Labels indexed by key.
|
||||||
|
type Map interface {
|
||||||
|
// Find returns the label that matches the supplied key.
|
||||||
|
Find(key Key) Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is the interface to something that provides an iterable
|
||||||
|
// list of labels.
|
||||||
|
// Iteration should start from 0 and continue until Valid returns false.
|
||||||
|
type List interface {
|
||||||
|
// Valid returns true if the index is within range for the list.
|
||||||
|
// It does not imply the label at that index will itself be valid.
|
||||||
|
Valid(index int) bool
|
||||||
|
// Label returns the label at the given index.
|
||||||
|
Label(index int) Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// list implements LabelList for a list of Labels.
|
||||||
|
type list struct {
|
||||||
|
labels []Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter wraps a LabelList filtering out specific labels.
|
||||||
|
type filter struct {
|
||||||
|
keys []Key
|
||||||
|
underlying List
|
||||||
|
}
|
||||||
|
|
||||||
|
// listMap implements LabelMap for a simple list of labels.
|
||||||
|
type listMap struct {
|
||||||
|
labels []Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapChain implements LabelMap for a list of underlying LabelMap.
|
||||||
|
type mapChain struct {
|
||||||
|
maps []Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// OfValue creates a new label from the key and value.
|
||||||
|
// This method is for implementing new key types, label creation should
|
||||||
|
// normally be done with the Of method of the key.
|
||||||
|
func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} }
|
||||||
|
|
||||||
|
// UnpackValue assumes the label was built using LabelOfValue and returns the value
|
||||||
|
// that was passed to that constructor.
|
||||||
|
// This method is for implementing new key types, for type safety normal
|
||||||
|
// access should be done with the From method of the key.
|
||||||
|
func (t Label) UnpackValue() interface{} { return t.untyped }
|
||||||
|
|
||||||
|
// Of64 creates a new label from a key and a uint64. This is often
|
||||||
|
// used for non uint64 values that can be packed into a uint64.
|
||||||
|
// This method is for implementing new key types, label creation should
|
||||||
|
// normally be done with the Of method of the key.
|
||||||
|
func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} }
|
||||||
|
|
||||||
|
// Unpack64 assumes the label was built using LabelOf64 and returns the value that
|
||||||
|
// was passed to that constructor.
|
||||||
|
// This method is for implementing new key types, for type safety normal
|
||||||
|
// access should be done with the From method of the key.
|
||||||
|
func (t Label) Unpack64() uint64 { return t.packed }
|
||||||
|
|
||||||
|
// OfString creates a new label from a key and a string.
|
||||||
|
// This method is for implementing new key types, label creation should
|
||||||
|
// normally be done with the Of method of the key.
|
||||||
|
func OfString(k Key, v string) Label {
|
||||||
|
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
|
||||||
|
return Label{
|
||||||
|
key: k,
|
||||||
|
packed: uint64(hdr.Len),
|
||||||
|
untyped: unsafe.Pointer(hdr.Data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackString assumes the label was built using LabelOfString and returns the
|
||||||
|
// value that was passed to that constructor.
|
||||||
|
// This method is for implementing new key types, for type safety normal
|
||||||
|
// access should be done with the From method of the key.
|
||||||
|
func (t Label) UnpackString() string {
|
||||||
|
var v string
|
||||||
|
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
|
||||||
|
hdr.Data = uintptr(t.untyped.(unsafe.Pointer))
|
||||||
|
hdr.Len = int(t.packed)
|
||||||
|
return *(*string)(unsafe.Pointer(hdr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if the Label is a valid one (it has a key).
|
||||||
|
func (t Label) Valid() bool { return t.key != nil }
|
||||||
|
|
||||||
|
// Key returns the key of this Label.
|
||||||
|
func (t Label) Key() Key { return t.key }
|
||||||
|
|
||||||
|
// Format is used for debug printing of labels.
|
||||||
|
func (t Label) Format(f fmt.State, r rune) {
|
||||||
|
if !t.Valid() {
|
||||||
|
io.WriteString(f, `nil`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
io.WriteString(f, t.Key().Name())
|
||||||
|
io.WriteString(f, "=")
|
||||||
|
var buf [128]byte
|
||||||
|
t.Key().Format(f, buf[:0], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *list) Valid(index int) bool {
|
||||||
|
return index >= 0 && index < len(l.labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *list) Label(index int) Label {
|
||||||
|
return l.labels[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filter) Valid(index int) bool {
|
||||||
|
return f.underlying.Valid(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filter) Label(index int) Label {
|
||||||
|
l := f.underlying.Label(index)
|
||||||
|
for _, f := range f.keys {
|
||||||
|
if l.Key() == f {
|
||||||
|
return Label{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm listMap) Find(key Key) Label {
|
||||||
|
for _, l := range lm.labels {
|
||||||
|
if l.Key() == key {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Label{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c mapChain) Find(key Key) Label {
|
||||||
|
for _, src := range c.maps {
|
||||||
|
l := src.Find(key)
|
||||||
|
if l.Valid() {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Label{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyList = &list{}
|
||||||
|
|
||||||
|
func NewList(labels ...Label) List {
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return emptyList
|
||||||
|
}
|
||||||
|
return &list{labels: labels}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Filter(l List, keys ...Key) List {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return &filter{keys: keys, underlying: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMap(labels ...Label) Map {
|
||||||
|
return listMap{labels: labels}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeMaps(srcs ...Map) Map {
|
||||||
|
var nonNil []Map
|
||||||
|
for _, src := range srcs {
|
||||||
|
if src != nil {
|
||||||
|
nonNil = append(nonNil, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nonNil) == 1 {
|
||||||
|
return nonNil[0]
|
||||||
|
}
|
||||||
|
return mapChain{maps: nonNil}
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gocommand is a helper for calling the go command.
|
||||||
|
package gocommand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Runner will run go command invocations and serialize
|
||||||
|
// them if it sees a concurrency error.
|
||||||
|
type Runner struct {
|
||||||
|
// once guards the runner initialization.
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
|
// inFlight tracks available workers.
|
||||||
|
inFlight chan struct{}
|
||||||
|
|
||||||
|
// serialized guards the ability to run a go command serially,
|
||||||
|
// to avoid deadlocks when claiming workers.
|
||||||
|
serialized chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxInFlight = 10
|
||||||
|
|
||||||
|
func (runner *Runner) initialize() {
|
||||||
|
runner.once.Do(func() {
|
||||||
|
runner.inFlight = make(chan struct{}, maxInFlight)
|
||||||
|
runner.serialized = make(chan struct{}, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.13: go: updates to go.mod needed, but contents have changed
|
||||||
|
// 1.14: go: updating go.mod: existing contents have changed since last read
|
||||||
|
var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
|
||||||
|
|
||||||
|
// Run is a convenience wrapper around RunRaw.
|
||||||
|
// It returns only stdout and a "friendly" error.
|
||||||
|
func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
|
||||||
|
stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
|
||||||
|
return stdout, friendly
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunPiped runs the invocation serially, always waiting for any concurrent
|
||||||
|
// invocations to complete first.
|
||||||
|
func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
|
||||||
|
_, err := runner.runPiped(ctx, inv, stdout, stderr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunRaw runs the invocation, serializing requests only if they fight over
|
||||||
|
// go.mod changes.
|
||||||
|
func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
|
||||||
|
// Make sure the runner is always initialized.
|
||||||
|
runner.initialize()
|
||||||
|
|
||||||
|
// First, try to run the go command concurrently.
|
||||||
|
stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
|
||||||
|
|
||||||
|
// If we encounter a load concurrency error, we need to retry serially.
|
||||||
|
if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
|
||||||
|
return stdout, stderr, friendlyErr, err
|
||||||
|
}
|
||||||
|
event.Error(ctx, "Load concurrency error, will retry serially", err)
|
||||||
|
|
||||||
|
// Run serially by calling runPiped.
|
||||||
|
stdout.Reset()
|
||||||
|
stderr.Reset()
|
||||||
|
friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
|
||||||
|
return stdout, stderr, friendlyErr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
|
||||||
|
// Wait for 1 worker to become available.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, nil, nil, ctx.Err()
|
||||||
|
case runner.inFlight <- struct{}{}:
|
||||||
|
defer func() { <-runner.inFlight }()
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
|
||||||
|
friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
|
||||||
|
return stdout, stderr, friendlyErr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
|
||||||
|
// Make sure the runner is always initialized.
|
||||||
|
runner.initialize()
|
||||||
|
|
||||||
|
// Acquire the serialization lock. This avoids deadlocks between two
|
||||||
|
// runPiped commands.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case runner.serialized <- struct{}{}:
|
||||||
|
defer func() { <-runner.serialized }()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all in-progress go commands to return before proceeding,
|
||||||
|
// to avoid load concurrency errors.
|
||||||
|
for i := 0; i < maxInFlight; i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case runner.inFlight <- struct{}{}:
|
||||||
|
// Make sure we always "return" any workers we took.
|
||||||
|
defer func() { <-runner.inFlight }()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inv.runWithFriendlyError(ctx, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Invocation represents a call to the go command.
|
||||||
|
type Invocation struct {
|
||||||
|
Verb string
|
||||||
|
Args []string
|
||||||
|
BuildFlags []string
|
||||||
|
Env []string
|
||||||
|
WorkingDir string
|
||||||
|
Logf func(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
|
||||||
|
rawError = i.run(ctx, stdout, stderr)
|
||||||
|
if rawError != nil {
|
||||||
|
friendlyError = rawError
|
||||||
|
// Check for 'go' executable not being found.
|
||||||
|
if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
||||||
|
friendlyError = fmt.Errorf("go command required, not found: %v", ee)
|
||||||
|
}
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
friendlyError = ctx.Err()
|
||||||
|
}
|
||||||
|
friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
|
||||||
|
log := i.Logf
|
||||||
|
if log == nil {
|
||||||
|
log = func(string, ...interface{}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
goArgs := []string{i.Verb}
|
||||||
|
switch i.Verb {
|
||||||
|
case "mod":
|
||||||
|
// mod needs the sub-verb before build flags.
|
||||||
|
goArgs = append(goArgs, i.Args[0])
|
||||||
|
goArgs = append(goArgs, i.BuildFlags...)
|
||||||
|
goArgs = append(goArgs, i.Args[1:]...)
|
||||||
|
case "env":
|
||||||
|
// env doesn't take build flags.
|
||||||
|
goArgs = append(goArgs, i.Args...)
|
||||||
|
default:
|
||||||
|
goArgs = append(goArgs, i.BuildFlags...)
|
||||||
|
goArgs = append(goArgs, i.Args...)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("go", goArgs...)
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
// On darwin the cwd gets resolved to the real path, which breaks anything that
|
||||||
|
// expects the working directory to keep the original path, including the
|
||||||
|
// go command when dealing with modules.
|
||||||
|
// The Go stdlib has a special feature where if the cwd and the PWD are the
|
||||||
|
// same node then it trusts the PWD, so by setting it in the env for the child
|
||||||
|
// process we fix up all the paths returned by the go command.
|
||||||
|
cmd.Env = append(os.Environ(), i.Env...)
|
||||||
|
if i.WorkingDir != "" {
|
||||||
|
cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
|
||||||
|
cmd.Dir = i.WorkingDir
|
||||||
|
}
|
||||||
|
defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
|
||||||
|
|
||||||
|
return runCmdContext(ctx, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCmdContext is like exec.CommandContext except it sends os.Interrupt
|
||||||
|
// before os.Kill.
|
||||||
|
func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
resChan <- cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-resChan:
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
// Cancelled. Interrupt and see if it ends voluntarily.
|
||||||
|
cmd.Process.Signal(os.Interrupt)
|
||||||
|
select {
|
||||||
|
case err := <-resChan:
|
||||||
|
return err
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
// Didn't shut down in response to interrupt. Kill it hard.
|
||||||
|
cmd.Process.Kill()
|
||||||
|
return <-resChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDebugStr(cmd *exec.Cmd) string {
|
||||||
|
env := make(map[string]string)
|
||||||
|
for _, kv := range cmd.Env {
|
||||||
|
split := strings.Split(kv, "=")
|
||||||
|
k, v := split[0], split[1]
|
||||||
|
env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gocommand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModuleJSON holds information about a module.
|
||||||
|
type ModuleJSON struct {
|
||||||
|
Path string // module path
|
||||||
|
Replace *ModuleJSON // replaced by this module
|
||||||
|
Main bool // is this the main module?
|
||||||
|
Indirect bool // is this module only an indirect dependency of main module?
|
||||||
|
Dir string // directory holding files for this module, if any
|
||||||
|
GoMod string // path to go.mod file for this module, if any
|
||||||
|
GoVersion string // go version used in module
|
||||||
|
}
|
||||||
|
|
||||||
|
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
|
||||||
|
|
||||||
|
// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
|
||||||
|
// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
|
||||||
|
// of which only Verb and Args are modified to run the appropriate Go command.
|
||||||
|
// Inspired by setDefaultBuildMod in modload/init.go
|
||||||
|
func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
|
||||||
|
mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check the GOFLAGS to see if there is anything overridden or not.
|
||||||
|
inv.Verb = "env"
|
||||||
|
inv.Args = []string{"GOFLAGS"}
|
||||||
|
stdout, err := r.Run(ctx, inv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
goflags := string(bytes.TrimSpace(stdout.Bytes()))
|
||||||
|
matches := modFlagRegexp.FindStringSubmatch(goflags)
|
||||||
|
var modFlag string
|
||||||
|
if len(matches) != 0 {
|
||||||
|
modFlag = matches[1]
|
||||||
|
}
|
||||||
|
if modFlag != "" {
|
||||||
|
// Don't override an explicit '-mod=' argument.
|
||||||
|
return mainMod, modFlag == "vendor", nil
|
||||||
|
}
|
||||||
|
if mainMod == nil || !go114 {
|
||||||
|
return mainMod, false, nil
|
||||||
|
}
|
||||||
|
// Check 1.14's automatic vendor mode.
|
||||||
|
if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
|
||||||
|
if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
|
||||||
|
// The Go version is at least 1.14, and a vendor directory exists.
|
||||||
|
// Set -mod=vendor by default.
|
||||||
|
return mainMod, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mainMod, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMainModuleAnd114 gets the main module's information and whether the
|
||||||
|
// go command in use is 1.14+. This is the information needed to figure out
|
||||||
|
// if vendoring should be enabled.
|
||||||
|
func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
|
||||||
|
const format = `{{.Path}}
|
||||||
|
{{.Dir}}
|
||||||
|
{{.GoMod}}
|
||||||
|
{{.GoVersion}}
|
||||||
|
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
|
||||||
|
`
|
||||||
|
inv.Verb = "list"
|
||||||
|
inv.Args = []string{"-m", "-f", format}
|
||||||
|
stdout, err := r.Run(ctx, inv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(stdout.String(), "\n")
|
||||||
|
if len(lines) < 5 {
|
||||||
|
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
|
||||||
|
}
|
||||||
|
mod := &ModuleJSON{
|
||||||
|
Path: lines[0],
|
||||||
|
Dir: lines[1],
|
||||||
|
GoMod: lines[2],
|
||||||
|
GoVersion: lines[3],
|
||||||
|
Main: true,
|
||||||
|
}
|
||||||
|
return mod, lines[4] == "go1.14", nil
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Package packagesinternal exposes internal-only fields from go/packages.
|
||||||
|
package packagesinternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
var GetForTest = func(p interface{}) string { return "" }
|
||||||
|
|
||||||
|
var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil }
|
||||||
|
|
||||||
|
var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {}
|
||||||
|
|
||||||
|
var TypecheckCgo int
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package typesinternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/types"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetUsesCgo(conf *types.Config) bool {
|
||||||
|
v := reflect.ValueOf(conf).Elem()
|
||||||
|
|
||||||
|
f := v.FieldByName("go115UsesCgo")
|
||||||
|
if !f.IsValid() {
|
||||||
|
f = v.FieldByName("UsesCgo")
|
||||||
|
if !f.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := unsafe.Pointer(f.UnsafeAddr())
|
||||||
|
*(*bool)(addr) = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google 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,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,2 @@
|
||||||
|
This repository holds the transition packages for the new Go 1.13 error values.
|
||||||
|
See golang.org/design/29934-error-values.
|
|
@ -0,0 +1,193 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatError calls the FormatError method of f with an errors.Printer
|
||||||
|
// configured according to s and verb, and writes the result to s.
|
||||||
|
func FormatError(f Formatter, s fmt.State, verb rune) {
|
||||||
|
// Assuming this function is only called from the Format method, and given
|
||||||
|
// that FormatError takes precedence over Format, it cannot be called from
|
||||||
|
// any package that supports errors.Formatter. It is therefore safe to
|
||||||
|
// disregard that State may be a specific printer implementation and use one
|
||||||
|
// of our choice instead.
|
||||||
|
|
||||||
|
// limitations: does not support printing error as Go struct.
|
||||||
|
|
||||||
|
var (
|
||||||
|
sep = " " // separator before next error
|
||||||
|
p = &state{State: s}
|
||||||
|
direct = true
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error = f
|
||||||
|
|
||||||
|
switch verb {
|
||||||
|
// Note that this switch must match the preference order
|
||||||
|
// for ordinary string printing (%#v before %+v, and so on).
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('#') {
|
||||||
|
if stringer, ok := err.(fmt.GoStringer); ok {
|
||||||
|
io.WriteString(&p.buf, stringer.GoString())
|
||||||
|
goto exit
|
||||||
|
}
|
||||||
|
// proceed as if it were %v
|
||||||
|
} else if s.Flag('+') {
|
||||||
|
p.printDetail = true
|
||||||
|
sep = "\n - "
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
case 'q', 'x', 'X':
|
||||||
|
// Use an intermediate buffer in the rare cases that precision,
|
||||||
|
// truncation, or one of the alternative verbs (q, x, and X) are
|
||||||
|
// specified.
|
||||||
|
direct = false
|
||||||
|
|
||||||
|
default:
|
||||||
|
p.buf.WriteString("%!")
|
||||||
|
p.buf.WriteRune(verb)
|
||||||
|
p.buf.WriteByte('(')
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
p.buf.WriteString(reflect.TypeOf(f).String())
|
||||||
|
default:
|
||||||
|
p.buf.WriteString("<nil>")
|
||||||
|
}
|
||||||
|
p.buf.WriteByte(')')
|
||||||
|
io.Copy(s, &p.buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
switch v := err.(type) {
|
||||||
|
case Formatter:
|
||||||
|
err = v.FormatError((*printer)(p))
|
||||||
|
case fmt.Formatter:
|
||||||
|
v.Format(p, 'v')
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
io.WriteString(&p.buf, v.Error())
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p.needColon || !p.printDetail {
|
||||||
|
p.buf.WriteByte(':')
|
||||||
|
p.needColon = false
|
||||||
|
}
|
||||||
|
p.buf.WriteString(sep)
|
||||||
|
p.inDetail = false
|
||||||
|
p.needNewline = false
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
width, okW := s.Width()
|
||||||
|
prec, okP := s.Precision()
|
||||||
|
|
||||||
|
if !direct || (okW && width > 0) || okP {
|
||||||
|
// Construct format string from State s.
|
||||||
|
format := []byte{'%'}
|
||||||
|
if s.Flag('-') {
|
||||||
|
format = append(format, '-')
|
||||||
|
}
|
||||||
|
if s.Flag('+') {
|
||||||
|
format = append(format, '+')
|
||||||
|
}
|
||||||
|
if s.Flag(' ') {
|
||||||
|
format = append(format, ' ')
|
||||||
|
}
|
||||||
|
if okW {
|
||||||
|
format = strconv.AppendInt(format, int64(width), 10)
|
||||||
|
}
|
||||||
|
if okP {
|
||||||
|
format = append(format, '.')
|
||||||
|
format = strconv.AppendInt(format, int64(prec), 10)
|
||||||
|
}
|
||||||
|
format = append(format, string(verb)...)
|
||||||
|
fmt.Fprintf(s, string(format), p.buf.String())
|
||||||
|
} else {
|
||||||
|
io.Copy(s, &p.buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var detailSep = []byte("\n ")
|
||||||
|
|
||||||
|
// state tracks error printing state. It implements fmt.State.
|
||||||
|
type state struct {
|
||||||
|
fmt.State
|
||||||
|
buf bytes.Buffer
|
||||||
|
|
||||||
|
printDetail bool
|
||||||
|
inDetail bool
|
||||||
|
needColon bool
|
||||||
|
needNewline bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) Write(b []byte) (n int, err error) {
|
||||||
|
if s.printDetail {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if s.inDetail && s.needColon {
|
||||||
|
s.needNewline = true
|
||||||
|
if b[0] == '\n' {
|
||||||
|
b = b[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k := 0
|
||||||
|
for i, c := range b {
|
||||||
|
if s.needNewline {
|
||||||
|
if s.inDetail && s.needColon {
|
||||||
|
s.buf.WriteByte(':')
|
||||||
|
s.needColon = false
|
||||||
|
}
|
||||||
|
s.buf.Write(detailSep)
|
||||||
|
s.needNewline = false
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
s.buf.Write(b[k:i])
|
||||||
|
k = i + 1
|
||||||
|
s.needNewline = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.buf.Write(b[k:])
|
||||||
|
if !s.inDetail {
|
||||||
|
s.needColon = true
|
||||||
|
}
|
||||||
|
} else if !s.inDetail {
|
||||||
|
s.buf.Write(b)
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printer wraps a state to implement an xerrors.Printer.
|
||||||
|
type printer state
|
||||||
|
|
||||||
|
func (s *printer) Print(args ...interface{}) {
|
||||||
|
if !s.inDetail || s.printDetail {
|
||||||
|
fmt.Fprint((*state)(s), args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *printer) Printf(format string, args ...interface{}) {
|
||||||
|
if !s.inDetail || s.printDetail {
|
||||||
|
fmt.Fprintf((*state)(s), format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *printer) Detail() bool {
|
||||||
|
s.inDetail = true
|
||||||
|
return s.printDetail
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
issuerepo: golang/go
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package xerrors implements functions to manipulate errors.
|
||||||
|
//
|
||||||
|
// This package is based on the Go 2 proposal for error values:
|
||||||
|
// https://golang.org/design/29934-error-values
|
||||||
|
//
|
||||||
|
// These functions were incorporated into the standard library's errors package
|
||||||
|
// in Go 1.13:
|
||||||
|
// - Is
|
||||||
|
// - As
|
||||||
|
// - Unwrap
|
||||||
|
//
|
||||||
|
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
|
||||||
|
//
|
||||||
|
// Use this package to get equivalent behavior in all supported Go versions.
|
||||||
|
//
|
||||||
|
// No other features of this package were included in Go 1.13, and at present
|
||||||
|
// there are no plans to include any of them.
|
||||||
|
package xerrors // import "golang.org/x/xerrors"
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// errorString is a trivial implementation of error.
|
||||||
|
type errorString struct {
|
||||||
|
s string
|
||||||
|
frame Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an error that formats as the given text.
|
||||||
|
//
|
||||||
|
// The returned error contains a Frame set to the caller's location and
|
||||||
|
// implements Formatter to show this information when printed with details.
|
||||||
|
func New(text string) error {
|
||||||
|
return &errorString{text, Caller(1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorString) Error() string {
|
||||||
|
return e.s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||||
|
|
||||||
|
func (e *errorString) FormatError(p Printer) (next error) {
|
||||||
|
p.Print(e.s)
|
||||||
|
e.frame.Format(p)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const percentBangString = "%!"
|
||||||
|
|
||||||
|
// Errorf formats according to a format specifier and returns the string as a
|
||||||
|
// value that satisfies error.
|
||||||
|
//
|
||||||
|
// The returned error includes the file and line number of the caller when
|
||||||
|
// formatted with additional detail enabled. If the last argument is an error
|
||||||
|
// the returned error's Format method will return it if the format string ends
|
||||||
|
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
|
||||||
|
// format string ends with ": %w", the returned error implements an Unwrap
|
||||||
|
// method returning it.
|
||||||
|
//
|
||||||
|
// If the format specifier includes a %w verb with an error operand in a
|
||||||
|
// position other than at the end, the returned error will still implement an
|
||||||
|
// Unwrap method returning the operand, but the error's Format method will not
|
||||||
|
// return the wrapped error.
|
||||||
|
//
|
||||||
|
// It is invalid to include more than one %w verb or to supply it with an
|
||||||
|
// operand that does not implement the error interface. The %w verb is otherwise
|
||||||
|
// a synonym for %v.
|
||||||
|
func Errorf(format string, a ...interface{}) error {
|
||||||
|
format = formatPlusW(format)
|
||||||
|
// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
|
||||||
|
wrap := strings.HasSuffix(format, ": %w")
|
||||||
|
idx, format2, ok := parsePercentW(format)
|
||||||
|
percentWElsewhere := !wrap && idx >= 0
|
||||||
|
if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) {
|
||||||
|
err := errorAt(a, len(a)-1)
|
||||||
|
if err == nil {
|
||||||
|
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
|
||||||
|
}
|
||||||
|
// TODO: this is not entirely correct. The error value could be
|
||||||
|
// printed elsewhere in format if it mixes numbered with unnumbered
|
||||||
|
// substitutions. With relatively small changes to doPrintf we can
|
||||||
|
// have it optionally ignore extra arguments and pass the argument
|
||||||
|
// list in its entirety.
|
||||||
|
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
|
||||||
|
frame := Frame{}
|
||||||
|
if internal.EnableTrace {
|
||||||
|
frame = Caller(1)
|
||||||
|
}
|
||||||
|
if wrap {
|
||||||
|
return &wrapError{msg, err, frame}
|
||||||
|
}
|
||||||
|
return &noWrapError{msg, err, frame}
|
||||||
|
}
|
||||||
|
// Support %w anywhere.
|
||||||
|
// TODO: don't repeat the wrapped error's message when %w occurs in the middle.
|
||||||
|
msg := fmt.Sprintf(format2, a...)
|
||||||
|
if idx < 0 {
|
||||||
|
return &noWrapError{msg, nil, Caller(1)}
|
||||||
|
}
|
||||||
|
err := errorAt(a, idx)
|
||||||
|
if !ok || err == nil {
|
||||||
|
// Too many %ws or argument of %w is not an error. Approximate the Go
|
||||||
|
// 1.13 fmt.Errorf message.
|
||||||
|
return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)}
|
||||||
|
}
|
||||||
|
frame := Frame{}
|
||||||
|
if internal.EnableTrace {
|
||||||
|
frame = Caller(1)
|
||||||
|
}
|
||||||
|
return &wrapError{msg, err, frame}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorAt(args []interface{}, i int) error {
|
||||||
|
if i < 0 || i >= len(args) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err, ok := args[i].(error)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPlusW is used to avoid the vet check that will barf at %w.
|
||||||
|
func formatPlusW(s string) string {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the index of the only %w in format, or -1 if none.
|
||||||
|
// Also return a rewritten format string with %w replaced by %v, and
|
||||||
|
// false if there is more than one %w.
|
||||||
|
// TODO: handle "%[N]w".
|
||||||
|
func parsePercentW(format string) (idx int, newFormat string, ok bool) {
|
||||||
|
// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
|
||||||
|
idx = -1
|
||||||
|
ok = true
|
||||||
|
n := 0
|
||||||
|
sz := 0
|
||||||
|
var isW bool
|
||||||
|
for i := 0; i < len(format); i += sz {
|
||||||
|
if format[i] != '%' {
|
||||||
|
sz = 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// "%%" is not a format directive.
|
||||||
|
if i+1 < len(format) && format[i+1] == '%' {
|
||||||
|
sz = 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sz, isW = parsePrintfVerb(format[i:])
|
||||||
|
if isW {
|
||||||
|
if idx >= 0 {
|
||||||
|
ok = false
|
||||||
|
} else {
|
||||||
|
idx = n
|
||||||
|
}
|
||||||
|
// "Replace" the last character, the 'w', with a 'v'.
|
||||||
|
p := i + sz - 1
|
||||||
|
format = format[:p] + "v" + format[p+1:]
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return idx, format, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the printf verb starting with a % at s[0].
|
||||||
|
// Return how many bytes it occupies and whether the verb is 'w'.
|
||||||
|
func parsePrintfVerb(s string) (int, bool) {
|
||||||
|
// Assume only that the directive is a sequence of non-letters followed by a single letter.
|
||||||
|
sz := 0
|
||||||
|
var r rune
|
||||||
|
for i := 1; i < len(s); i += sz {
|
||||||
|
r, sz = utf8.DecodeRuneInString(s[i:])
|
||||||
|
if unicode.IsLetter(r) {
|
||||||
|
return i + sz, r == 'w'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(s), false
|
||||||
|
}
|
||||||
|
|
||||||
|
type noWrapError struct {
|
||||||
|
msg string
|
||||||
|
err error
|
||||||
|
frame Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *noWrapError) Error() string {
|
||||||
|
return fmt.Sprint(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||||
|
|
||||||
|
func (e *noWrapError) FormatError(p Printer) (next error) {
|
||||||
|
p.Print(e.msg)
|
||||||
|
e.frame.Format(p)
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapError struct {
|
||||||
|
msg string
|
||||||
|
err error
|
||||||
|
frame Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wrapError) Error() string {
|
||||||
|
return fmt.Sprint(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||||
|
|
||||||
|
func (e *wrapError) FormatError(p Printer) (next error) {
|
||||||
|
p.Print(e.msg)
|
||||||
|
e.frame.Format(p)
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wrapError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
// A Formatter formats error messages.
|
||||||
|
type Formatter interface {
|
||||||
|
error
|
||||||
|
|
||||||
|
// FormatError prints the receiver's first error and returns the next error in
|
||||||
|
// the error chain, if any.
|
||||||
|
FormatError(p Printer) (next error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Printer formats error messages.
|
||||||
|
//
|
||||||
|
// The most common implementation of Printer is the one provided by package fmt
|
||||||
|
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
|
||||||
|
// typically provide their own implementations.
|
||||||
|
type Printer interface {
|
||||||
|
// Print appends args to the message output.
|
||||||
|
Print(args ...interface{})
|
||||||
|
|
||||||
|
// Printf writes a formatted string.
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
|
||||||
|
// Detail reports whether error detail is requested.
|
||||||
|
// After the first call to Detail, all text written to the Printer
|
||||||
|
// is formatted as additional detail, or ignored when
|
||||||
|
// detail has not been requested.
|
||||||
|
// If Detail returns false, the caller can avoid printing the detail at all.
|
||||||
|
Detail() bool
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Frame contains part of a call stack.
|
||||||
|
type Frame struct {
|
||||||
|
// Make room for three PCs: the one we were asked for, what it called,
|
||||||
|
// and possibly a PC for skipPleaseUseCallersFrames. See:
|
||||||
|
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
|
||||||
|
frames [3]uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller returns a Frame that describes a frame on the caller's stack.
|
||||||
|
// The argument skip is the number of frames to skip over.
|
||||||
|
// Caller(0) returns the frame for the caller of Caller.
|
||||||
|
func Caller(skip int) Frame {
|
||||||
|
var s Frame
|
||||||
|
runtime.Callers(skip+1, s.frames[:])
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// location reports the file, line, and function of a frame.
|
||||||
|
//
|
||||||
|
// The returned function may be "" even if file and line are not.
|
||||||
|
func (f Frame) location() (function, file string, line int) {
|
||||||
|
frames := runtime.CallersFrames(f.frames[:])
|
||||||
|
if _, ok := frames.Next(); !ok {
|
||||||
|
return "", "", 0
|
||||||
|
}
|
||||||
|
fr, ok := frames.Next()
|
||||||
|
if !ok {
|
||||||
|
return "", "", 0
|
||||||
|
}
|
||||||
|
return fr.Function, fr.File, fr.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format prints the stack as error detail.
|
||||||
|
// It should be called from an error's Format implementation
|
||||||
|
// after printing any other error detail.
|
||||||
|
func (f Frame) Format(p Printer) {
|
||||||
|
if p.Detail() {
|
||||||
|
function, file, line := f.location()
|
||||||
|
if function != "" {
|
||||||
|
p.Printf("%s\n ", function)
|
||||||
|
}
|
||||||
|
if file != "" {
|
||||||
|
p.Printf("%s:%d\n", file, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module golang.org/x/xerrors
|
||||||
|
|
||||||
|
go 1.11
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
// EnableTrace indicates whether stack information should be recorded in errors.
|
||||||
|
var EnableTrace = true
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xerrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Wrapper provides context around another error.
|
||||||
|
type Wrapper interface {
|
||||||
|
// Unwrap returns the next error in the error chain.
|
||||||
|
// If there is no next error, Unwrap returns nil.
|
||||||
|
Unwrap() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opaque returns an error with the same error formatting as err
|
||||||
|
// but that does not match err and cannot be unwrapped.
|
||||||
|
func Opaque(err error) error {
|
||||||
|
return noWrapper{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noWrapper struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e noWrapper) FormatError(p Printer) (next error) {
|
||||||
|
if f, ok := e.error.(Formatter); ok {
|
||||||
|
return f.FormatError(p)
|
||||||
|
}
|
||||||
|
p.Print(e.error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the result of calling the Unwrap method on err, if err implements
|
||||||
|
// Unwrap. Otherwise, Unwrap returns nil.
|
||||||
|
func Unwrap(err error) error {
|
||||||
|
u, ok := err.(Wrapper)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.Unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is reports whether any error in err's chain matches target.
|
||||||
|
//
|
||||||
|
// An error is considered to match a target if it is equal to that target or if
|
||||||
|
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
if target == nil {
|
||||||
|
return err == target
|
||||||
|
}
|
||||||
|
|
||||||
|
isComparable := reflect.TypeOf(target).Comparable()
|
||||||
|
for {
|
||||||
|
if isComparable && err == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// TODO: consider supporing target.Is(err). This would allow
|
||||||
|
// user-definable predicates, but also may allow for coping with sloppy
|
||||||
|
// APIs, thereby making it easier to get away with them.
|
||||||
|
if err = Unwrap(err); err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As finds the first error in err's chain that matches the type to which target
|
||||||
|
// points, and if so, sets the target to its value and returns true. An error
|
||||||
|
// matches a type if it is assignable to the target type, or if it has a method
|
||||||
|
// As(interface{}) bool such that As(target) returns true. As will panic if target
|
||||||
|
// is not a non-nil pointer to a type which implements error or is of interface type.
|
||||||
|
//
|
||||||
|
// The As method should set the target to its value and return true if err
|
||||||
|
// matches the type to which target points.
|
||||||
|
func As(err error, target interface{}) bool {
|
||||||
|
if target == nil {
|
||||||
|
panic("errors: target cannot be nil")
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(target)
|
||||||
|
typ := val.Type()
|
||||||
|
if typ.Kind() != reflect.Ptr || val.IsNil() {
|
||||||
|
panic("errors: target must be a non-nil pointer")
|
||||||
|
}
|
||||||
|
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
|
||||||
|
panic("errors: *target must be interface or implement error")
|
||||||
|
}
|
||||||
|
targetType := typ.Elem()
|
||||||
|
for err != nil {
|
||||||
|
if reflect.TypeOf(err).AssignableTo(targetType) {
|
||||||
|
val.Elem().Set(reflect.ValueOf(err))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err = Unwrap(err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
Loading…
Reference in New Issue