AJ ONeal
4 years ago
77 changed files with 15091 additions and 0 deletions
@ -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 |
@ -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, " ") |
|||
} |
@ -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
@ -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
@ -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 |
|||
} |
@ -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) |
|||
} |
@ -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
@ -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
@ -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