diff --git a/repo-infra/.gitignore b/repo-infra/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bf5a9a8c7002f4297fb81deb64e718cbbe1f26f8 --- /dev/null +++ b/repo-infra/.gitignore @@ -0,0 +1,6 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +/bazel-* diff --git a/repo-infra/.kazelcfg.json b/repo-infra/.kazelcfg.json new file mode 100644 index 0000000000000000000000000000000000000000..a8b53943e897d29dcaf29cf08ba41aaaf0247fed --- /dev/null +++ b/repo-infra/.kazelcfg.json @@ -0,0 +1,3 @@ +{ + "GoPrefix": "k8s.io/repo-infra" +} diff --git a/repo-infra/.travis.yml b/repo-infra/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..8c2f38499f2410a53f9a283832c06ce050fcb553 --- /dev/null +++ b/repo-infra/.travis.yml @@ -0,0 +1,23 @@ +dist: trusty +sudo: required + +# Install Bazel and set up GOPATH. +before_install: +- go get -u github.com/alecthomas/gometalinter +- wget https://github.com/bazelbuild/bazel/releases/download/0.4.4/bazel_0.4.4-linux-x86_64.deb +- sudo dpkg -i bazel_0.4.4-linux-x86_64.deb +- mkdir -p $GOPATH/src/k8s.io +- mv $TRAVIS_BUILD_DIR $GOPATH/src/k8s.io +- cd $GOPATH/src/k8s.io/repo-infra + +install: + - gometalinter --install + - go get -u github.com/bazelbuild/buildifier/buildifier + - go install ./... + +script: + - verify/verify-boilerplate.sh --rootdir="$GOPATH/src/k8s.io/repo-infra" -v + - verify/verify-go-src.sh --rootdir "$GOPATH/src/k8s.io/repo-infra" -v + - buildifier -mode=check $(find . -name BUILD -o -name '*.bzl' -type f) + - kazel --print-diff --validate + - bazel build //... diff --git a/repo-infra/BUILD b/repo-infra/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..ca598fef1613500b416e58c34c784d2ab937eab9 --- /dev/null +++ b/repo-infra/BUILD @@ -0,0 +1,7 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_prefix") + +go_prefix("k8s.io/repo-infra") diff --git a/repo-infra/LICENSE b/repo-infra/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8dada3edaf50dbc082c9a125058f25def75e625a --- /dev/null +++ b/repo-infra/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/repo-infra/README.md b/repo-infra/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7139318d83b7099cb0ec18f308657006e88125c5 --- /dev/null +++ b/repo-infra/README.md @@ -0,0 +1,61 @@ +# Kubernetes repository infrastructure + +This repository contains repository infrastructure tools for use in +`kubernetes` and `kubernetes-incubator` repositories. Examples: + +- Boilerplate verification +- Go source code quality verification +- Golang build infrastructure + +--- + +## Using this repository + +This repository can be used via some golang "vendoring" mechanism +(such as glide), or it can be used via +[git subtree](http://git.kernel.org/cgit/git/git.git/plain/contrib/subtree/git-subtree.txt). + +### Using "vendoring" + +The exact mechanism to pull in this repository will vary depending on +the tool you use. However, unless you end up having this repository +at the root of your project's repository you wll probably need to +make sure you use the `--rootdir` command line parameter to let the +`verify-boilerplate.sh` know its location, eg: + + verify-boilerplate.sh --rootdir=/home/myrepo + +### Using `git subtree` + +When using the git subtree mechanism, this repository should be placed in the +top level of your project. + +To add `repo-infra` to your repository, use the following commands from the +root directory of **your** repository. + +First, add a git remote for the `repo-infra` repository: + +``` +$ git remote add repo-infra git://github.com/kubernetes/repo-infra +``` + +This is not strictly necessary, but reduces the typing required for subsequent +commands. + +Next, use `git subtree add` to create a new subtree in the `repo-infra` +directory within your project: + +``` +$ git subtree add -P repo-infra repo-infra master --squash +``` + +After this command, you will have: + +1. A `repo-infra` directory in your project containing the content of **this** + project +2. 2 new commits in the active branch: + 1. A commit that squashes the git history of the `repo-infra` project + 2. A merge commit whose ancestors are: + 1. The `HEAD` of the branch prior to when you ran `git subtree add` + 2. The commit containing the squashed `repo-infra` commits + diff --git a/repo-infra/WORKSPACE b/repo-infra/WORKSPACE new file mode 100644 index 0000000000000000000000000000000000000000..c06197bf8c18c647d526b00be38f11f62b6cceb7 --- /dev/null +++ b/repo-infra/WORKSPACE @@ -0,0 +1,11 @@ +workspace(name = "io_kubernetes_build") + +git_repository( + name = "io_bazel_rules_go", + commit = "adfad77dabd529ed9d90a4e7b823323628e908d9", + remote = "https://github.com/bazelbuild/rules_go.git", +) + +load("@io_bazel_rules_go//go:def.bzl", "go_repositories") + +go_repositories() diff --git a/repo-infra/defs/BUILD b/repo-infra/defs/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..70e0e955b54348e3a6964a61c7d56046447364db --- /dev/null +++ b/repo-infra/defs/BUILD @@ -0,0 +1,8 @@ +package(default_visibility = ["//visibility:public"]) + +py_binary( + name = "gcs_uploader", + srcs = [ + "gcs_uploader.py", + ], +) diff --git a/repo-infra/defs/build.bzl b/repo-infra/defs/build.bzl new file mode 100644 index 0000000000000000000000000000000000000000..254707415eed7f5dcbea996d7f3e35e653368cd0 --- /dev/null +++ b/repo-infra/defs/build.bzl @@ -0,0 +1,119 @@ +def _gcs_upload_impl(ctx): + output_lines = [] + for t in ctx.attr.data: + label = str(t.label) + upload_path=ctx.attr.upload_paths.get(label, "") + for f in t.files: + output_lines.append("%s\t%s" % (f.short_path, upload_path)) + + ctx.file_action( + output = ctx.outputs.targets, + content = "\n".join(output_lines), + ) + + ctx.file_action( + content = "%s --manifest %s --root $PWD -- $@" % ( + ctx.attr.uploader.files_to_run.executable.short_path, + ctx.outputs.targets.short_path, + ), + output = ctx.outputs.executable, + executable = True, + ) + + return struct( + runfiles = ctx.runfiles( + files = ctx.files.data + ctx.files.uploader + + [ctx.info_file, ctx.version_file, ctx.outputs.targets] + ) + ) + +# Adds an executable rule to upload the specified artifacts to GCS. +# +# The keys in upload_paths must match the elaborated targets exactly; i.e., +# one must specify "//foo/bar:bar" and not just "//foo/bar". +# +# Both the upload_paths and the path supplied on the commandline can include +# Python format strings which will be replaced by values from the workspace status, +# e.g. gs://my-bucket-{BUILD_USER}/stash/{STABLE_BUILD_SCM_REVISION} +gcs_upload = rule( + attrs = { + "data": attr.label_list( + mandatory = True, + allow_files = True, + ), + "uploader": attr.label( + default = Label("//defs:gcs_uploader"), + allow_files = True, + ), + # TODO: combine with 'data' when label_keyed_string_dict is supported in Bazel + "upload_paths": attr.string_dict( + allow_empty = True, + ), + }, + executable = True, + outputs = { + "targets": "%{name}-targets.txt", + }, + implementation = _gcs_upload_impl, +) + +# Computes the md5sum of the provided src file, saving it in a file named 'name'. +def md5sum(name, src, visibility=None): + native.genrule( + name = name + "_genmd5sum", + srcs = [src], + outs = [name], + # Currently each go_binary target has two outputs (the binary and the library), + # so we hash both but only save the hash for the binary. + cmd = "for f in $(SRCS); do if command -v md5 >/dev/null; then md5 -q $$f>$@; else md5sum $$f | awk '{print $$1}' > $@; fi; done", + message = "Computing md5sum", + visibility = visibility, +) + +# Computes the sha1sum of the provided src file, saving it in a file named 'name'. +def sha1sum(name, src, visibility=None): + native.genrule( + name = name + "_gensha1sum", + srcs = [src], + outs = [name], + # Currently each go_binary target has two outputs (the binary and the library), + # so we hash both but only save the hash for the binary. + cmd = "command -v sha1sum >/dev/null && cmd=sha1sum || cmd='shasum -a1'; for f in $(SRCS); do $$cmd $$f | awk '{print $$1}' > $@; done", + message = "Computing sha1sum", + visibility = visibility, +) + +# Creates 3+N rules based on the provided targets: +# * A filegroup with just the provided targets (named 'name') +# * A filegroup containing all of the md5 and sha1 hash files ('name-hashes') +# * A filegroup containing both of the above ('name-and-hashes') +# * All of the necessary md5sum and sha1sum rules +def release_filegroup(name, srcs, visibility=None): + hashes = [] + for src in srcs: + parts = src.split(":") + if len(parts) > 1: + basename = parts[1] + else: + basename = src.split("/")[-1] + + md5sum(name=basename + ".md5", src=src, visibility=visibility) + hashes.append(basename + ".md5") + sha1sum(name=basename + ".sha1", src=src, visibility=visibility) + hashes.append(basename + ".sha1") + + native.filegroup( + name = name, + srcs = srcs, + visibility = visibility, + ) + native.filegroup( + name = name + "-hashes", + srcs = hashes, + visibility = visibility, + ) + native.filegroup( + name = name + "-and-hashes", + srcs = [name, name + "-hashes"], + visibility = visibility, + ) diff --git a/repo-infra/defs/deb.bzl b/repo-infra/defs/deb.bzl new file mode 100644 index 0000000000000000000000000000000000000000..f67aa1471659a06b128612978ec022d96a97aa1a --- /dev/null +++ b/repo-infra/defs/deb.bzl @@ -0,0 +1,34 @@ +load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar", "pkg_deb") + +KUBERNETES_AUTHORS = "Kubernetes Authors <kubernetes-dev+release@googlegroups.com>" + +KUBERNETES_HOMEPAGE = "http://kubernetes.io" + +def k8s_deb(name, depends = [], description = ""): + pkg_deb( + name = name, + architecture = "amd64", + data = name + "-data", + depends = depends, + description = description, + homepage = KUBERNETES_HOMEPAGE, + maintainer = KUBERNETES_AUTHORS, + package = name, + version = "1.6.0-alpha", + ) + +def deb_data(name, data = []): + deps = [] + for i, info in enumerate(data): + dname = "%s-deb-data-%s" % (name, i) + deps += [dname] + pkg_tar( + name = dname, + files = info["files"], + mode = info["mode"], + package_dir = info["dir"], + ) + pkg_tar( + name = name + "-data", + deps = deps, + ) diff --git a/repo-infra/defs/gcs_uploader.py b/repo-infra/defs/gcs_uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..4533953cbab7c2aef10a026d7127e21c4c9760c4 --- /dev/null +++ b/repo-infra/defs/gcs_uploader.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import atexit +import os +import os.path +import shutil +import subprocess +import sys +import tempfile + +def _workspace_status_dict(root): + d = {} + for f in ("stable-status.txt", "volatile-status.txt"): + with open(os.path.join(root, f)) as info_file: + for info_line in info_file: + info_line = info_line.strip("\n") + key, value = info_line.split(" ") + d[key] = value + return d + +def main(argv): + scratch = tempfile.mkdtemp(prefix="bazel-gcs.") + atexit.register(lambda: shutil.rmtree(scratch)) + + workspace_status = _workspace_status_dict(argv.root) + with open(argv.manifest) as manifest: + for artifact in manifest: + artifact = artifact.strip("\n") + src_file, dest_dir = artifact.split("\t") + dest_dir = dest_dir.format(**workspace_status) + scratch_dest_dir = os.path.join(scratch, dest_dir) + try: + os.makedirs(scratch_dest_dir) + except (OSError): + # skip directory already exists errors + pass + + src = os.path.join(argv.root, src_file) + dest = os.path.join(scratch_dest_dir, os.path.basename(src_file)) + os.symlink(src, dest) + + ret = 0 + uploaded_paths = [] + for gcs_path in argv.gcs_paths: + gcs_path = gcs_path.format(**workspace_status) + local_path = None + if gcs_path.startswith("file://"): + local_path = gcs_path[len("file://"):] + elif "://" not in gcs_path: + local_path = gcs_path + if local_path and not os.path.exists(local_path): + os.makedirs(local_path) + ret |= subprocess.call(["gsutil", "-m", "rsync", "-C", "-r", scratch, gcs_path]) + uploaded_paths.append(gcs_path) + print "Uploaded to %s" % " ".join(uploaded_paths) + sys.exit(ret) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Upload build targets to GCS.') + + parser.add_argument("--manifest", required=True, help="path to manifest of targets") + parser.add_argument("--root", required=True, help="path to root of workspace") + parser.add_argument("gcs_paths", nargs="+", help="path in gcs to push targets") + + main(parser.parse_args()) diff --git a/repo-infra/defs/go.bzl b/repo-infra/defs/go.bzl new file mode 100644 index 0000000000000000000000000000000000000000..fb1e9168fac33171bc46664c8e46b8e3d75cf12d --- /dev/null +++ b/repo-infra/defs/go.bzl @@ -0,0 +1,111 @@ +go_filetype = ["*.go"] + +def _compute_genrule_variables(resolved_srcs, resolved_outs): + variables = {"SRCS": cmd_helper.join_paths(" ", resolved_srcs), + "OUTS": cmd_helper.join_paths(" ", resolved_outs)} + if len(resolved_srcs) == 1: + variables["<"] = list(resolved_srcs)[0].path + if len(resolved_outs) == 1: + variables["@"] = list(resolved_outs)[0].path + return variables + +def _go_sources_aspect_impl(target, ctx): + transitive_sources = set(target.go_sources) + for dep in ctx.rule.attr.deps: + transitive_sources = transitive_sources | dep.transitive_sources + return struct(transitive_sources = transitive_sources) + +go_sources_aspect = aspect( + attr_aspects = ["deps"], + implementation = _go_sources_aspect_impl, +) + +def _compute_genrule_command(ctx): + cmd = [ + 'set -e', + # setup main GOPATH + 'export GOPATH=/tmp/gopath', + 'export GO_WORKSPACE=$${GOPATH}/src/' + ctx.attr.go_prefix.go_prefix, + 'mkdir -p $${GO_WORKSPACE%/*}', + 'ln -s $$(pwd) $${GO_WORKSPACE}', + # setup genfile GOPATH + 'export GENGOPATH=/tmp/gengopath', + 'export GENGO_WORKSPACE=$${GENGOPATH}/src/' + ctx.attr.go_prefix.go_prefix, + 'mkdir -p $${GENGO_WORKSPACE%/*}', + 'ln -s $$(pwd)/$(GENDIR) $${GENGO_WORKSPACE}', + # drop into WORKSPACE + 'export GOPATH=$${GOPATH}:$${GENGOPATH}', + 'cd $${GO_WORKSPACE}', + # execute user command + ctx.attr.cmd.strip(' \t\n\r'), + ] + return '\n'.join(cmd) + +def _go_genrule_impl(ctx): + all_srcs = set(ctx.attr._go_toolchain.stdlib) + label_dict = {} + + for dep in ctx.attr.go_deps: + all_srcs = all_srcs | dep.transitive_sources + + for dep in ctx.attr.srcs: + all_srcs = all_srcs | dep.files + label_dict[dep.label] = dep.files + + cmd = _compute_genrule_command(ctx) + + resolved_inputs, argv, runfiles_manifests = ctx.resolve_command( + command=cmd, + attribute="cmd", + expand_locations=True, + make_variables=_compute_genrule_variables(all_srcs, set(ctx.outputs.outs)), + tools=ctx.attr.tools, + label_dict=label_dict + ) + + ctx.action( + inputs = list(all_srcs) + resolved_inputs, + outputs = ctx.outputs.outs, + env = ctx.configuration.default_shell_env + ctx.attr._go_toolchain.env, + command = argv, + progress_message = "%s %s" % (ctx.attr.message, ctx), + mnemonic = "GoGenrule", + ) + +# We have codegen procedures that depend on the "go/*" stdlib packages +# and thus depend on executing with a valid GOROOT and GOPATH containing +# some amount transitive go src of dependencies. This go_genrule enables +# the creation of these sandboxes. +go_genrule = rule( + attrs = { + "srcs": attr.label_list(allow_files = True), + "tools": attr.label_list( + cfg = "host", + allow_files = True, + ), + "outs": attr.output_list(mandatory = True), + "cmd": attr.string(mandatory = True), + "go_deps": attr.label_list( + aspects = [go_sources_aspect], + ), + "message": attr.string(), + "executable": attr.bool(default = False), + # Next two rules copied from bazelbuild/rules_go@a9df110cf04e167b33f10473c7e904d780d921e6 + # and then modified a bit. + # These will likely break at some point in the future, pending Bazel toolchain changes. + "_go_toolchain": attr.label( + default = Label("@io_bazel_rules_go_toolchain//:go_toolchain"), + ), + "go_prefix": attr.label( + providers = ["go_prefix"], + default = Label( + "//:go_prefix", + relative_to_caller_repository = True, + ), + allow_files = False, + cfg = "host", + ), + }, + output_to_genfiles = True, + implementation = _go_genrule_impl, +) diff --git a/repo-infra/kazel/BUILD b/repo-infra/kazel/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..5e0bb5dc949726662b8be4576f19165dcef9c870 --- /dev/null +++ b/repo-infra/kazel/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", +) + +go_binary( + name = "kazel", + library = ":go_default_library", + tags = ["automanaged"], +) + +go_library( + name = "go_default_library", + srcs = [ + "config.go", + "diff.go", + "generator.go", + "kazel.go", + "sourcerer.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor:github.com/bazelbuild/buildifier/core", + "//vendor:github.com/golang/glog", + "//vendor:go/path/filepath", + ], +) diff --git a/repo-infra/kazel/README.rst b/repo-infra/kazel/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..2cc96aee7cda7270d05a018726f09e6e053bcf74 --- /dev/null +++ b/repo-infra/kazel/README.rst @@ -0,0 +1,90 @@ +kazel - a BUILD file generator for go and bazel +=============================================== + +Requirements: +############# + +* Your project must be somewhat compatible with go tool because + kazel uses go tool to parse your import tree. +* You must have a **GOPATH** and **GOROOT** setup and your project must + be in the correct location in your **GOPATH**. +* Your ``./vendor`` directory may not contain ``BUILD`` files. + +Usage: +###### + +1. Get kazel by running ``go get k8s.io/repo-infra/kazel``. + +2. Create a ``.kazelcfg.json`` in the root of the repository. For the + kazel repository, the ``.kazelcfg.json`` would look like: + + .. code-block:: json + + { + "GoPrefix": "k8s.io/repo-infra", + "SrcDirs": [ + "./kazel" + ], + "SkippedPaths": [ + ".*foobar(baz)?.*$" + ] + } + +3. Run kazel: + + .. code-block:: bash + + $ kazel -root=$GOPATH/src/k8s.io/repo-infra + +Defaults: +######### + +* **SrcDirs** in ``.kazelcfg.json`` defaults to ``["./"]`` +* ``-root`` option defaults to the current working directory + +Automanagement: +############### + +kazel reconciles rules that have the "**automanaged**" tag. If +you no longer want kazel to manage a rule, you can remove the +**automanaged** tag and kazel will no longer manage that rule. + +kazel only manages srcs, deps, and library attributes of a +rule after initial creation so you can add and managed other +attributes like data and copts and kazel will respect your +changes. + +kazel automatically formats all ``BUILD`` files in your repository +except for those matching **SkippedPaths**. + +Adding "sources" rules: +####################### + +If you set "**AddSourcesRules**": ``true`` in your ``.kazelcfg.json``, +kazel will create "**package-srcs**" and "**all-srcs**" rules in every +package. + +The "**package-srcs**" rule is a glob matching all files in the +package recursively, but not any files owned by packages in +subdirectories. + +The "**all-srcs**" rule includes both the "**package-srcs**" rule and +the "**all-srcs**" rules of all subpackages; i.e. **//:all-srcs** will +include all files in your repository. + +The "**package-srcs**" rule defaults to private visibility, +since it is safer to depend on the "**all-srcs**" rule: if a +subpackage is added, the "**package-srcs**" rule will no longer +include those files. + +You can remove the "**automanaged**" tag from the "**package-srcs**" +rule if you need to modify the glob (such as adding excludes). +It's recommended that you leave the "**all-srcs**" rule +automanaged. + +Validating BUILD files in CI: +############################# + +If you run kazel with ``--validate``, it will not update any ``BUILD`` files, but it +will exit nonzero if any ``BUILD`` files are out-of-date. You can add ``--print-diff`` +to print out the changes needed. diff --git a/repo-infra/kazel/config.go b/repo-infra/kazel/config.go new file mode 100644 index 0000000000000000000000000000000000000000..3b00a6bad2f732148997064fe9fc108a2dece2a8 --- /dev/null +++ b/repo-infra/kazel/config.go @@ -0,0 +1,58 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "encoding/json" + "io/ioutil" +) + +// Cfg defines the configuration options for kazel. +type Cfg struct { + GoPrefix string + // evaluated recursively, defaults to ["."] + SrcDirs []string + // regexps that match packages to skip + SkippedPaths []string + // whether to add "pkg-srcs" and "all-srcs" filegroups + // note that this operates on the entire tree (not just SrcsDirs) but skips anything matching SkippedPaths + AddSourcesRules bool + // whether to have multiple build files in vendor/ or just one. + VendorMultipleBuildFiles bool + // whether to manage kubernetes' pkg/generated/openapi. + K8sOpenAPIGen bool +} + +// ReadCfg reads and unmarshals the specified json file into a Cfg struct. +func ReadCfg(cfgPath string) (*Cfg, error) { + b, err := ioutil.ReadFile(cfgPath) + if err != nil { + return nil, err + } + var cfg Cfg + if err := json.Unmarshal(b, &cfg); err != nil { + return nil, err + } + defaultCfg(&cfg) + return &cfg, nil +} + +func defaultCfg(c *Cfg) { + if len(c.SrcDirs) == 0 { + c.SrcDirs = []string{"."} + } +} diff --git a/repo-infra/kazel/diff.go b/repo-infra/kazel/diff.go new file mode 100644 index 0000000000000000000000000000000000000000..37bed9381cd7ebd2e4a7ca904df130009e6d3ce2 --- /dev/null +++ b/repo-infra/kazel/diff.go @@ -0,0 +1,60 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "io/ioutil" + "os" + "os/exec" +) + +// Diff prints the unified diff of the two provided byte slices +// using the unix diff command. +func Diff(left, right []byte) error { + lf, err := ioutil.TempFile("/tmp", "actual-file-") + if err != nil { + return err + } + defer lf.Close() + defer os.Remove(lf.Name()) + + rf, err := ioutil.TempFile("/tmp", "expected-file-") + if err != nil { + return err + } + defer rf.Close() + defer os.Remove(rf.Name()) + + _, err = lf.Write(left) + if err != nil { + return err + } + lf.Close() + + _, err = rf.Write(right) + if err != nil { + return err + } + rf.Close() + + cmd := exec.Command("/usr/bin/diff", "-u", lf.Name(), rf.Name()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + + return nil +} diff --git a/repo-infra/kazel/generator.go b/repo-infra/kazel/generator.go new file mode 100644 index 0000000000000000000000000000000000000000..c7879d04dfb74cc00d1216c6fc0ec7aec2b875af --- /dev/null +++ b/repo-infra/kazel/generator.go @@ -0,0 +1,117 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +const ( + openAPIGenTag = "// +k8s:openapi-gen" + + baseImport = "k8s.io/kubernetes/" + staging = "staging/src/" +) + +// walkGenerated updates the rule for kubernetes' OpenAPI generated file. +// This involves reading all go files in the source tree and looking for the +// "+k8s:openapi-gen" tag. If present, then that package must be supplied to +// the genrule. +func (v *Vendorer) walkGenerated() error { + if !v.cfg.K8sOpenAPIGen { + return nil + } + v.managedAttrs = append(v.managedAttrs, "openapi_targets", "vendor_targets") + paths, err := v.findOpenAPI(v.root) + if err != nil { + return err + } + return v.addGeneratedOpenAPIRule(paths) +} + +// findOpenAPI searches for all packages under root that request OpenAPI. It +// returns the go import paths. It does not follow symlinks. +func (v *Vendorer) findOpenAPI(root string) ([]string, error) { + finfos, err := ioutil.ReadDir(root) + if err != nil { + return nil, err + } + var res []string + var includeMe bool + for _, finfo := range finfos { + path := filepath.Join(root, finfo.Name()) + if finfo.IsDir() && (finfo.Mode()&os.ModeSymlink == 0) { + children, err := v.findOpenAPI(path) + if err != nil { + return nil, err + } + res = append(res, children...) + } else if strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go") { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + if bytes.Contains(b, []byte(openAPIGenTag)) { + includeMe = true + } + } + } + if includeMe { + pkg, err := v.ctx.ImportDir(root, 0) + if err != nil { + return nil, err + } + res = append(res, pkg.ImportPath) + } + return res, nil +} + +// addGeneratedOpenAPIRule updates the pkg/generated/openapi go_default_library +// rule with the automanaged openapi_targets and vendor_targets. +func (v *Vendorer) addGeneratedOpenAPIRule(paths []string) error { + var openAPITargets []string + var vendorTargets []string + for _, p := range paths { + if !strings.HasPrefix(p, baseImport) { + return fmt.Errorf("openapi-gen path outside of kubernetes: %s", p) + } + np := p[len(baseImport):] + if strings.HasPrefix(np, staging) { + vendorTargets = append(vendorTargets, np[len(staging):]) + } else { + openAPITargets = append(openAPITargets, np) + } + } + sort.Strings(openAPITargets) + sort.Strings(vendorTargets) + + pkgPath := filepath.Join("pkg", "generated", "openapi") + for _, r := range v.newRules[pkgPath] { + if r.Name() == "go_default_library" { + r.SetAttr("openapi_targets", asExpr(openAPITargets)) + r.SetAttr("vendor_targets", asExpr(vendorTargets)) + break + } + } + return nil +} diff --git a/repo-infra/kazel/kazel.go b/repo-infra/kazel/kazel.go new file mode 100644 index 0000000000000000000000000000000000000000..8a61b79b62d2f620458c1dadcdc354eaee20dce3 --- /dev/null +++ b/repo-infra/kazel/kazel.go @@ -0,0 +1,781 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/build" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "regexp" + "runtime" + "sort" + "strings" + + "go/path/filepath" + + bzl "github.com/bazelbuild/buildifier/core" + "github.com/golang/glog" +) + +const ( + vendorPath = "vendor/" + automanagedTag = "automanaged" +) + +var ( + root = flag.String("root", ".", "root of go source") + dryRun = flag.Bool("dry-run", false, "run in dry mode") + printDiff = flag.Bool("print-diff", false, "print diff to stdout") + validate = flag.Bool("validate", false, "run in dry mode and exit nonzero if any BUILD files need to be updated") + cfgPath = flag.String("cfg-path", ".kazelcfg.json", "path to kazel config (relative paths interpreted relative to -repo.") +) + +func main() { + flag.Parse() + flag.Set("alsologtostderr", "true") + if *root == "" { + glog.Fatalf("-root argument is required") + } + if *validate { + *dryRun = true + } + v, err := newVendorer(*root, *cfgPath, *dryRun) + if err != nil { + glog.Fatalf("unable to build vendorer: %v", err) + } + if err = os.Chdir(v.root); err != nil { + glog.Fatalf("cannot chdir into root %q: %v", v.root, err) + } + + if err = v.walkVendor(); err != nil { + glog.Fatalf("err walking vendor: %v", err) + } + if err = v.walkRepo(); err != nil { + glog.Fatalf("err walking repo: %v", err) + } + if err = v.walkGenerated(); err != nil { + glog.Fatalf("err walking generated: %v", err) + } + if _, err = v.walkSource("."); err != nil { + glog.Fatalf("err walking source: %v", err) + } + written := 0 + if written, err = v.reconcileAllRules(); err != nil { + glog.Fatalf("err reconciling rules: %v", err) + } + if *validate && written > 0 { + fmt.Fprintf(os.Stderr, "\n%d BUILD files not up-to-date.\n", written) + os.Exit(1) + } +} + +// Vendorer collects context, configuration, and cache while walking the tree. +type Vendorer struct { + ctx *build.Context + icache map[icacheKey]icacheVal + skippedPaths []*regexp.Regexp + dryRun bool + root string + cfg *Cfg + newRules map[string][]*bzl.Rule // package path -> list of rules to add or update + managedAttrs []string +} + +func newVendorer(root, cfgPath string, dryRun bool) (*Vendorer, error) { + absRoot, err := filepath.Abs(root) + if err != nil { + return nil, fmt.Errorf("could not get absolute path: %v", err) + } + if !filepath.IsAbs(cfgPath) { + cfgPath = filepath.Join(absRoot, cfgPath) + } + cfg, err := ReadCfg(cfgPath) + if err != nil { + return nil, err + } + + v := Vendorer{ + ctx: context(), + dryRun: dryRun, + root: absRoot, + icache: map[icacheKey]icacheVal{}, + cfg: cfg, + newRules: make(map[string][]*bzl.Rule), + managedAttrs: []string{"srcs", "deps", "library"}, + } + + for _, sp := range cfg.SkippedPaths { + r, err := regexp.Compile(sp) + if err != nil { + return nil, err + } + v.skippedPaths = append(v.skippedPaths, r) + } + for _, builtinSkip := range []string{ + "^\\.git", + "^bazel-*", + } { + v.skippedPaths = append(v.skippedPaths, regexp.MustCompile(builtinSkip)) + } + + return &v, nil + +} + +type icacheKey struct { + path, srcDir string +} + +type icacheVal struct { + pkg *build.Package + err error +} + +func (v *Vendorer) importPkg(path string, srcDir string) (*build.Package, error) { + k := icacheKey{path: path, srcDir: srcDir} + if val, ok := v.icache[k]; ok { + return val.pkg, val.err + } + + // cache miss + pkg, err := v.ctx.Import(path, srcDir, build.ImportComment) + v.icache[k] = icacheVal{pkg: pkg, err: err} + return pkg, err +} + +func writeHeaders(file *bzl.File) { + pkgRule := bzl.Rule{ + Call: &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: "package"}, + }, + } + pkgRule.SetAttr("default_visibility", asExpr([]string{"//visibility:public"})) + + file.Stmt = append(file.Stmt, + []bzl.Expr{ + pkgRule.Call, + &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: "licenses"}, + List: []bzl.Expr{asExpr([]string{"notice"})}, + }, + &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: "load"}, + List: asExpr([]string{ + "@io_bazel_rules_go//go:def.bzl", + }).(*bzl.ListExpr).List, + }, + }..., + ) +} + +func writeRules(file *bzl.File, rules []*bzl.Rule) { + for _, rule := range rules { + file.Stmt = append(file.Stmt, rule.Call) + } +} + +func (v *Vendorer) resolve(ipath string) Label { + if ipath == v.cfg.GoPrefix { + return Label{ + tag: "go_default_library", + } + } else if strings.HasPrefix(ipath, v.cfg.GoPrefix) { + return Label{ + pkg: strings.TrimPrefix(ipath, v.cfg.GoPrefix+"/"), + tag: "go_default_library", + } + } + if v.cfg.VendorMultipleBuildFiles { + return Label{ + pkg: "vendor/" + ipath, + tag: "go_default_library", + } + } + return Label{ + pkg: "vendor", + tag: ipath, + } +} + +func (v *Vendorer) walk(root string, f func(path, ipath string, pkg *build.Package) error) error { + skipVendor := true + if root == vendorPath { + skipVendor = false + } + return sfilepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + return nil + } + if skipVendor && strings.HasPrefix(path, vendorPath) { + return filepath.SkipDir + } + for _, r := range v.skippedPaths { + if r.Match([]byte(path)) { + return filepath.SkipDir + } + } + ipath, err := filepath.Rel(root, path) + if err != nil { + return err + } + pkg, err := v.importPkg(".", filepath.Join(v.root, path)) + if err != nil { + if _, ok := err.(*build.NoGoError); err != nil && ok { + return nil + } + return err + } + + return f(path, ipath, pkg) + }) +} + +func (v *Vendorer) walkRepo() error { + for _, root := range v.cfg.SrcDirs { + if err := v.walk(root, v.updatePkg); err != nil { + return err + } + } + return nil +} + +func (v *Vendorer) updateSinglePkg(path string) error { + pkg, err := v.importPkg(".", "./"+path) + if err != nil { + if _, ok := err.(*build.NoGoError); err != nil && ok { + return nil + } + return err + } + return v.updatePkg(path, "", pkg) +} + +type ruleType int + +// The RuleType* constants enumerate the bazel rules supported by this tool. +const ( + RuleTypeGoBinary ruleType = iota + RuleTypeGoLibrary + RuleTypeGoTest + RuleTypeGoXTest + RuleTypeCGoGenrule + RuleTypeFileGroup + RuleTypeOpenAPILibrary +) + +// RuleKind converts a value of the RuleType* enum into the BUILD string. +func (rt ruleType) RuleKind() string { + switch rt { + case RuleTypeGoBinary: + return "go_binary" + case RuleTypeGoLibrary: + return "go_library" + case RuleTypeGoTest: + return "go_test" + case RuleTypeGoXTest: + return "go_test" + case RuleTypeCGoGenrule: + return "cgo_genrule" + case RuleTypeFileGroup: + return "filegroup" + case RuleTypeOpenAPILibrary: + return "openapi_library" + } + panic("unreachable") +} + +// NamerFunc is a function that returns the appropriate name for the rule for the provided RuleType. +type NamerFunc func(ruleType) string + +func (v *Vendorer) updatePkg(path, _ string, pkg *build.Package) error { + + srcNameMap := func(srcs ...[]string) *bzl.ListExpr { + return asExpr(merge(srcs...)).(*bzl.ListExpr) + } + + srcs := srcNameMap(pkg.GoFiles, pkg.SFiles) + cgoSrcs := srcNameMap(pkg.CgoFiles, pkg.CFiles, pkg.CXXFiles, pkg.HFiles) + testSrcs := srcNameMap(pkg.TestGoFiles) + xtestSrcs := srcNameMap(pkg.XTestGoFiles) + + v.addRules(path, v.emit(srcs, cgoSrcs, testSrcs, xtestSrcs, pkg, func(rt ruleType) string { + switch rt { + case RuleTypeGoBinary: + return filepath.Base(pkg.Dir) + case RuleTypeGoLibrary: + return "go_default_library" + case RuleTypeGoTest: + return "go_default_test" + case RuleTypeGoXTest: + return "go_default_xtest" + case RuleTypeCGoGenrule: + return "cgo_codegen" + } + panic("unreachable") + })) + + return nil +} + +func (v *Vendorer) emit(srcs, cgoSrcs, testSrcs, xtestSrcs *bzl.ListExpr, pkg *build.Package, namer NamerFunc) []*bzl.Rule { + var goLibAttrs = make(Attrs) + var rules []*bzl.Rule + + deps := v.extractDeps(pkg.Imports) + + if len(srcs.List) >= 0 { + goLibAttrs.Set("srcs", srcs) + } else if len(cgoSrcs.List) == 0 { + return nil + } + + if len(deps.List) > 0 { + goLibAttrs.SetList("deps", deps) + } + + if pkg.IsCommand() { + rules = append(rules, newRule(RuleTypeGoBinary, namer, map[string]bzl.Expr{ + "library": asExpr(":" + namer(RuleTypeGoLibrary)), + })) + } + + addGoDefaultLibrary := len(cgoSrcs.List) > 0 || len(srcs.List) > 0 + if len(cgoSrcs.List) != 0 { + cgoRuleAttrs := make(Attrs) + + cgoRuleAttrs.SetList("srcs", cgoSrcs) + cgoRuleAttrs.SetList("clinkopts", asExpr([]string{"-lz", "-lm", "-lpthread", "-ldl"}).(*bzl.ListExpr)) + + rules = append(rules, newRule(RuleTypeCGoGenrule, namer, cgoRuleAttrs)) + + goLibAttrs.Set("library", asExpr(":"+namer(RuleTypeCGoGenrule))) + } + + if len(testSrcs.List) != 0 { + testRuleAttrs := make(Attrs) + + testRuleAttrs.SetList("srcs", testSrcs) + testRuleAttrs.SetList("deps", v.extractDeps(pkg.TestImports)) + + if addGoDefaultLibrary { + testRuleAttrs.Set("library", asExpr(":"+namer(RuleTypeGoLibrary))) + } + rules = append(rules, newRule(RuleTypeGoTest, namer, testRuleAttrs)) + } + + if addGoDefaultLibrary { + rules = append(rules, newRule(RuleTypeGoLibrary, namer, goLibAttrs)) + } + + if len(xtestSrcs.List) != 0 { + xtestRuleAttrs := make(Attrs) + + xtestRuleAttrs.SetList("srcs", xtestSrcs) + xtestRuleAttrs.SetList("deps", v.extractDeps(pkg.XTestImports)) + + rules = append(rules, newRule(RuleTypeGoXTest, namer, xtestRuleAttrs)) + } + + return rules +} + +func (v *Vendorer) addRules(pkgPath string, rules []*bzl.Rule) { + cleanPath := filepath.Clean(pkgPath) + v.newRules[cleanPath] = append(v.newRules[cleanPath], rules...) +} + +func (v *Vendorer) walkVendor() error { + var rules []*bzl.Rule + updateFunc := func(path, ipath string, pkg *build.Package) error { + srcNameMap := func(srcs ...[]string) *bzl.ListExpr { + return asExpr( + apply( + merge(srcs...), + mapper(func(s string) string { + return strings.TrimPrefix(filepath.Join(path, s), "vendor/") + }), + ), + ).(*bzl.ListExpr) + } + + srcs := srcNameMap(pkg.GoFiles, pkg.SFiles) + cgoSrcs := srcNameMap(pkg.CgoFiles, pkg.CFiles, pkg.CXXFiles, pkg.HFiles) + testSrcs := srcNameMap(pkg.TestGoFiles) + xtestSrcs := srcNameMap(pkg.XTestGoFiles) + + tagBase := v.resolve(ipath).tag + + rules = append(rules, v.emit(srcs, cgoSrcs, testSrcs, xtestSrcs, pkg, func(rt ruleType) string { + switch rt { + case RuleTypeGoBinary: + return tagBase + "_bin" + case RuleTypeGoLibrary: + return tagBase + case RuleTypeGoTest: + return tagBase + "_test" + case RuleTypeGoXTest: + return tagBase + "_xtest" + case RuleTypeCGoGenrule: + return tagBase + "_cgo" + } + panic("unreachable") + })...) + + return nil + } + if v.cfg.VendorMultipleBuildFiles { + updateFunc = v.updatePkg + } + if err := v.walk(vendorPath, updateFunc); err != nil { + return err + } + v.addRules(vendorPath, rules) + + return nil +} + +func (v *Vendorer) extractDeps(deps []string) *bzl.ListExpr { + return asExpr( + apply( + merge(deps), + filterer(func(s string) bool { + pkg, err := v.importPkg(s, v.root) + if err != nil { + if strings.Contains(err.Error(), `cannot find package "C"`) || + // added in go1.7 + strings.Contains(err.Error(), `cannot find package "context"`) || + strings.Contains(err.Error(), `cannot find package "net/http/httptrace"`) { + return false + } + fmt.Fprintf(os.Stderr, "extract err: %v\n", err) + return false + } + if pkg.Goroot { + return false + } + return true + }), + mapper(func(s string) string { + return v.resolve(s).String() + }), + ), + ).(*bzl.ListExpr) +} + +func (v *Vendorer) reconcileAllRules() (int, error) { + var paths []string + for path := range v.newRules { + paths = append(paths, path) + } + sort.Strings(paths) + written := 0 + for _, path := range paths { + w, err := ReconcileRules(path, v.newRules[path], v.managedAttrs, v.dryRun) + if w { + written++ + } + if err != nil { + return written, err + } + } + return written, nil +} + +// Attrs collects the attributes for a rule. +type Attrs map[string]bzl.Expr + +// Set sets the named attribute to the provided bazel expression. +func (a Attrs) Set(name string, expr bzl.Expr) { + a[name] = expr +} + +// SetList sets the named attribute to the provided bazel expression list. +func (a Attrs) SetList(name string, expr *bzl.ListExpr) { + if len(expr.List) == 0 { + return + } + a[name] = expr +} + +// Label defines a bazel label. +type Label struct { + pkg, tag string +} + +func (l Label) String() string { + return fmt.Sprintf("//%v:%v", l.pkg, l.tag) +} + +func asExpr(e interface{}) bzl.Expr { + rv := reflect.ValueOf(e) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return &bzl.LiteralExpr{Token: fmt.Sprintf("%d", e)} + case reflect.Float32, reflect.Float64: + return &bzl.LiteralExpr{Token: fmt.Sprintf("%f", e)} + case reflect.String: + return &bzl.StringExpr{Value: e.(string)} + case reflect.Slice, reflect.Array: + var list []bzl.Expr + for i := 0; i < rv.Len(); i++ { + list = append(list, asExpr(rv.Index(i).Interface())) + } + return &bzl.ListExpr{List: list} + default: + glog.Fatalf("Uh oh") + return nil + } +} + +type sed func(s []string) []string + +func mapString(in []string, f func(string) string) []string { + var out []string + for _, s := range in { + out = append(out, f(s)) + } + return out +} + +func mapper(f func(string) string) sed { + return func(in []string) []string { + return mapString(in, f) + } +} + +func filterString(in []string, f func(string) bool) []string { + var out []string + for _, s := range in { + if f(s) { + out = append(out, s) + } + } + return out +} + +func filterer(f func(string) bool) sed { + return func(in []string) []string { + return filterString(in, f) + } +} + +func apply(stream []string, seds ...sed) []string { + for _, sed := range seds { + stream = sed(stream) + } + return stream +} + +func merge(streams ...[]string) []string { + var out []string + for _, stream := range streams { + out = append(out, stream...) + } + return out +} + +func newRule(rt ruleType, namer NamerFunc, attrs map[string]bzl.Expr) *bzl.Rule { + rule := &bzl.Rule{ + Call: &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: rt.RuleKind()}, + }, + } + rule.SetAttr("name", asExpr(namer(rt))) + for k, v := range attrs { + rule.SetAttr(k, v) + } + rule.SetAttr("tags", asExpr([]string{automanagedTag})) + return rule +} + +// findBuildFile determines the name of a preexisting BUILD file, returning +// a default if no such file exists. +func findBuildFile(pkgPath string) (bool, string) { + options := []string{"BUILD.bazel", "BUILD"} + for _, b := range options { + path := filepath.Join(pkgPath, b) + info, err := os.Stat(path) + if err == nil && !info.IsDir() { + return true, path + } + } + return false, filepath.Join(pkgPath, "BUILD") +} + +// ReconcileRules reconciles, simplifies, and writes the rules for the specified package, adding +// additional dependency rules as needed. +func ReconcileRules(pkgPath string, rules []*bzl.Rule, managedAttrs []string, dryRun bool) (bool, error) { + _, path := findBuildFile(pkgPath) + info, err := os.Stat(path) + if err != nil && os.IsNotExist(err) { + f := &bzl.File{} + writeHeaders(f) + reconcileLoad(f, rules) + writeRules(f, rules) + return writeFile(path, f, false, dryRun) + } else if err != nil { + return false, err + } + if info.IsDir() { + return false, fmt.Errorf("%q cannot be a directory", path) + } + b, err := ioutil.ReadFile(path) + if err != nil { + return false, err + } + f, err := bzl.Parse(path, b) + if err != nil { + return false, err + } + oldRules := make(map[string]*bzl.Rule) + for _, r := range f.Rules("") { + oldRules[r.Name()] = r + } + for _, r := range rules { + o, ok := oldRules[r.Name()] + if !ok { + f.Stmt = append(f.Stmt, r.Call) + continue + } + if !RuleIsManaged(o) { + continue + } + reconcileAttr := func(o, n *bzl.Rule, name string) { + if e := n.Attr(name); e != nil { + o.SetAttr(name, e) + } else { + o.DelAttr(name) + } + } + for _, attr := range managedAttrs { + reconcileAttr(o, r, attr) + } + delete(oldRules, r.Name()) + } + + for _, r := range oldRules { + if !RuleIsManaged(r) { + continue + } + f.DelRules(r.Kind(), r.Name()) + } + reconcileLoad(f, f.Rules("")) + + return writeFile(path, f, true, dryRun) +} + +func reconcileLoad(f *bzl.File, rules []*bzl.Rule) { + usedRuleKindsMap := map[string]bool{} + for _, r := range rules { + // Select only the Go rules we need to import, excluding builtins like filegroup. + // TODO: make less fragile + switch r.Kind() { + case "go_prefix", "go_library", "go_binary", "go_test", "go_proto_library", "cgo_genrule", "cgo_library": + usedRuleKindsMap[r.Kind()] = true + } + } + + usedRuleKindsList := []string{} + for k := range usedRuleKindsMap { + usedRuleKindsList = append(usedRuleKindsList, k) + } + sort.Strings(usedRuleKindsList) + + for _, r := range f.Rules("load") { + const goRulesLabel = "@io_bazel_rules_go//go:def.bzl" + args := bzl.Strings(&bzl.ListExpr{List: r.Call.List}) + if len(args) == 0 { + continue + } + if args[0] != goRulesLabel { + continue + } + if len(usedRuleKindsList) == 0 { + f.DelRules(r.Kind(), r.Name()) + continue + } + r.Call.List = asExpr(append( + []string{goRulesLabel}, usedRuleKindsList..., + )).(*bzl.ListExpr).List + break + } +} + +// RuleIsManaged returns whether the provided rule is managed by this tool, +// based on the tags set on the rule. +func RuleIsManaged(r *bzl.Rule) bool { + var automanaged bool + for _, tag := range r.AttrStrings("tags") { + if tag == automanagedTag { + automanaged = true + break + } + } + return automanaged +} + +func writeFile(path string, f *bzl.File, exists, dryRun bool) (bool, error) { + var info bzl.RewriteInfo + bzl.Rewrite(f, &info) + out := bzl.Format(f) + if exists { + orig, err := ioutil.ReadFile(path) + if err != nil { + return false, err + } + if bytes.Compare(orig, out) == 0 { + return false, nil + } + if *printDiff { + Diff(orig, out) + } + } + if dryRun { + fmt.Fprintf(os.Stderr, "DRY-RUN: wrote %q\n", path) + return true, nil + } + werr := ioutil.WriteFile(path, out, 0644) + if werr == nil { + fmt.Fprintf(os.Stderr, "wrote %q\n", path) + } + return werr == nil, werr +} + +func context() *build.Context { + return &build.Context{ + GOARCH: "amd64", + GOOS: "linux", + GOROOT: build.Default.GOROOT, + GOPATH: build.Default.GOPATH, + ReleaseTags: []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8"}, + Compiler: runtime.Compiler, + CgoEnabled: true, + } +} + +func walk(root string, walkFn filepath.WalkFunc) error { + return nil +} diff --git a/repo-infra/kazel/sourcerer.go b/repo-infra/kazel/sourcerer.go new file mode 100644 index 0000000000000000000000000000000000000000..77e5319df8da733346ac362d7714f607f11963c8 --- /dev/null +++ b/repo-infra/kazel/sourcerer.go @@ -0,0 +1,107 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + bzl "github.com/bazelbuild/buildifier/core" +) + +const ( + pkgSrcsTarget = "package-srcs" + allSrcsTarget = "all-srcs" +) + +// walkSource walks the source tree recursively from pkgPath, adding +// any BUILD files to v.newRules to be formatted. +// +// If AddSourcesRules is enabled in the kazel config, then we additionally add +// package-sources and recursive all-srcs filegroups rules to every BUILD file. +// +// Returns the list of children all-srcs targets that should be added to the +// all-srcs rule of the enclosing package. +func (v *Vendorer) walkSource(pkgPath string) ([]string, error) { + // clean pkgPath since we access v.newRules directly + pkgPath = filepath.Clean(pkgPath) + for _, r := range v.skippedPaths { + if r.Match([]byte(pkgPath)) { + return nil, nil + } + } + files, err := ioutil.ReadDir(pkgPath) + if err != nil { + return nil, err + } + + // Find any children packages we need to include in an all-srcs rule. + var children []string + for _, f := range files { + if f.IsDir() { + c, err := v.walkSource(filepath.Join(pkgPath, f.Name())) + if err != nil { + return nil, err + } + children = append(children, c...) + } + } + + // This path is a package either if we've added rules or if a BUILD file already exists. + _, hasRules := v.newRules[pkgPath] + isPkg := hasRules + if !isPkg { + isPkg, _ = findBuildFile(pkgPath) + } + + if !isPkg { + // This directory isn't a package (doesn't contain a BUILD file), + // but there might be subdirectories that are packages, + // so pass that up to our parent. + return children, nil + } + + // Enforce formatting the BUILD file, even if we're not adding srcs rules + if !hasRules { + v.addRules(pkgPath, nil) + } + + if !v.cfg.AddSourcesRules { + return nil, nil + } + + pkgSrcsExpr := &bzl.LiteralExpr{Token: `glob(["**"])`} + if pkgPath == "." { + pkgSrcsExpr = &bzl.LiteralExpr{Token: `glob(["**"], exclude=["bazel-*/**", ".git/**"])`} + } + + v.addRules(pkgPath, []*bzl.Rule{ + newRule(RuleTypeFileGroup, + func(_ ruleType) string { return pkgSrcsTarget }, + map[string]bzl.Expr{ + "srcs": pkgSrcsExpr, + "visibility": asExpr([]string{"//visibility:private"}), + }), + newRule(RuleTypeFileGroup, + func(_ ruleType) string { return allSrcsTarget }, + map[string]bzl.Expr{ + "srcs": asExpr(append(children, fmt.Sprintf(":%s", pkgSrcsTarget))), + }), + }) + return []string{fmt.Sprintf("//%s:%s", pkgPath, allSrcsTarget)}, nil +} diff --git a/repo-infra/vendor/BUILD b/repo-infra/vendor/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..7f9802d8b2c12dd2c28087b1d190bdd2b7142cd9 --- /dev/null +++ b/repo-infra/vendor/BUILD @@ -0,0 +1,39 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "github.com/bazelbuild/buildifier/core", + srcs = [ + "github.com/bazelbuild/buildifier/core/lex.go", + "github.com/bazelbuild/buildifier/core/parse.y.go", + "github.com/bazelbuild/buildifier/core/print.go", + "github.com/bazelbuild/buildifier/core/quote.go", + "github.com/bazelbuild/buildifier/core/rewrite.go", + "github.com/bazelbuild/buildifier/core/rule.go", + "github.com/bazelbuild/buildifier/core/syntax.go", + "github.com/bazelbuild/buildifier/core/tables.go", + "github.com/bazelbuild/buildifier/core/walk.go", + ], + tags = ["automanaged"], +) + +go_library( + name = "github.com/golang/glog", + srcs = [ + "github.com/golang/glog/glog.go", + "github.com/golang/glog/glog_file.go", + ], + tags = ["automanaged"], +) + +go_library( + name = "go/path/filepath", + srcs = ["go/path/filepath/path.go"], + tags = ["automanaged"], +) diff --git a/repo-infra/vendor/github.com/bazelbuild/LICENSE b/repo-infra/vendor/github.com/bazelbuild/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..0adcb5d6f52ab735c9f3cbe0f5635e5f6289dce5 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/build_defs.bzl b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/build_defs.bzl new file mode 100644 index 0000000000000000000000000000000000000000..82a50f409bb00f76ba50f3898290811b59e23d90 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/build_defs.bzl @@ -0,0 +1,14 @@ +_GO_TOOL = "@io_bazel_rules_go//go/toolchain:go_tool" + +def go_yacc(src, out, visibility=None): + native.genrule( + name = src + ".go_yacc", + srcs = [src], + outs = [out], + tools = [_GO_TOOL], + cmd = ("export GOROOT=$$(dirname $(location " + _GO_TOOL + "))/..;" + + " $(location " + _GO_TOOL + ") tool yacc " + + " -o $(location " + out + ") $(SRCS)"), + visibility = visibility, + local = 1, + ) diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/lex.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/lex.go new file mode 100644 index 0000000000000000000000000000000000000000..10677c2af0c8ed54777176e41293472f057a7d58 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/lex.go @@ -0,0 +1,769 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Lexical scanning for BUILD file parser. + +package build + +//go:generate go tool yacc -o parse.y.go parse.y + +import ( + "bytes" + "fmt" + "strings" + "unicode/utf8" +) + +// Parse parses the input data and returns the corresponding parse tree. +// +// The filename is used only for generating error messages. +func Parse(filename string, data []byte) (*File, error) { + in := newInput(filename, data) + return in.parse() +} + +// An input represents a single input file being parsed. +type input struct { + // Lexing state. + filename string // name of input file, for errors + complete []byte // entire input + remaining []byte // remaining input + token []byte // token being scanned + lastToken string // most recently returned token, for error messages + pos Position // current input position + comments []Comment // accumulated comments + endRule int // position of end of current rule + depth int // nesting of [ ] { } ( ) + + // Parser state. + file *File // returned top-level syntax tree + parseError error // error encountered during parsing + + // Comment assignment state. + pre []Expr // all expressions, in preorder traversal + post []Expr // all expressions, in postorder traversal +} + +func newInput(filename string, data []byte) *input { + return &input{ + filename: filename, + complete: data, + remaining: data, + pos: Position{Line: 1, LineRune: 1, Byte: 0}, + } +} + +// parse parses the input file. +func (in *input) parse() (f *File, err error) { + // The parser panics for both routine errors like syntax errors + // and for programmer bugs like array index errors. + // Turn both into error returns. Catching bug panics is + // especially important when processing many files. + defer func() { + if e := recover(); e != nil { + if e == in.parseError { + err = in.parseError + } else { + err = fmt.Errorf("%s:%d:%d: internal error: %v", in.filename, in.pos.Line, in.pos.LineRune, e) + } + } + }() + + // Invoke the parser generated from parse.y. + yyParse(in) + if in.parseError != nil { + return nil, in.parseError + } + in.file.Path = in.filename + + // Assign comments to nearby syntax. + in.assignComments() + + return in.file, nil +} + +// Error is called to report an error. +// When called by the generated code s is always "syntax error". +// Error does not return: it panics. +func (in *input) Error(s string) { + if s == "syntax error" && in.lastToken != "" { + s += " near " + in.lastToken + } + in.parseError = fmt.Errorf("%s:%d:%d: %v", in.filename, in.pos.Line, in.pos.LineRune, s) + panic(in.parseError) +} + +// eof reports whether the input has reached end of file. +func (in *input) eof() bool { + return len(in.remaining) == 0 +} + +// peekRune returns the next rune in the input without consuming it. +func (in *input) peekRune() int { + if len(in.remaining) == 0 { + return 0 + } + r, _ := utf8.DecodeRune(in.remaining) + return int(r) +} + +// readRune consumes and returns the next rune in the input. +func (in *input) readRune() int { + if len(in.remaining) == 0 { + in.Error("internal lexer error: readRune at EOF") + } + r, size := utf8.DecodeRune(in.remaining) + in.remaining = in.remaining[size:] + if r == '\n' { + in.pos.Line++ + in.pos.LineRune = 1 + } else { + in.pos.LineRune++ + } + in.pos.Byte += size + return int(r) +} + +// startToken marks the beginning of the next input token. +// It must be followed by a call to endToken, once the token has +// been consumed using readRune. +func (in *input) startToken(val *yySymType) { + in.token = in.remaining + val.tok = "" + val.pos = in.pos +} + +// yySymType (used in the next few functions) is defined by the +// generated parser. It is a struct containing all the fields listed +// in parse.y's %union [sic] section. + +// endToken marks the end of an input token. +// It records the actual token string in val.tok if the caller +// has not done that already. +func (in *input) endToken(val *yySymType) { + if val.tok == "" { + tok := string(in.token[:len(in.token)-len(in.remaining)]) + val.tok = tok + in.lastToken = val.tok + } +} + +// Lex is called from the generated parser to obtain the next input token. +// It returns the token value (either a rune like '+' or a symbolic token _FOR) +// and sets val to the data associated with the token. +// +// For all our input tokens, the associated data is +// val.Pos (the position where the token begins) +// and val.Token (the input string corresponding to the token). +func (in *input) Lex(val *yySymType) int { + // Skip past spaces, stopping at non-space or EOF. + countNL := 0 // number of newlines we've skipped past + for !in.eof() { + // The parser does not track indentation, because for the most part + // BUILD expressions don't care about how they are indented. + // However, we do need to be able to distinguish + // + // x = y[0] + // + // from the occasional + // + // x = y + // [0] + // + // To handle this one case, when we reach the beginning of a + // top-level BUILD expression, we scan forward to see where + // it should end and record the number of input bytes remaining + // at that endpoint. When we reach that point in the input, we + // insert an implicit semicolon to force the two expressions + // to stay separate. + // + if in.endRule != 0 && len(in.remaining) == in.endRule { + in.endRule = 0 + in.lastToken = "implicit ;" + val.tok = ";" + return ';' + } + + // Skip over spaces. Count newlines so we can give the parser + // information about where top-level blank lines are, + // for top-level comment assignment. + c := in.peekRune() + if c == ' ' || c == '\t' || c == '\r' || c == '\n' { + if c == '\n' && in.endRule == 0 { + // Not in a rule. Tell parser about top-level blank line. + in.startToken(val) + in.readRune() + in.endToken(val) + return '\n' + } + if c == '\n' { + countNL++ + } + in.readRune() + continue + } + + // Comment runs to end of line. + if c == '#' { + // Is this comment the only thing on its line? + // Find the last \n before this # and see if it's all + // spaces from there to here. + i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n")) + suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0 + + // Consume comment. + in.startToken(val) + for len(in.remaining) > 0 && in.readRune() != '\n' { + } + in.endToken(val) + + val.tok = strings.TrimRight(val.tok, "\n") + in.lastToken = "comment" + + // If we are at top level (not in a rule), hand the comment to + // the parser as a _COMMENT token. The grammar is written + // to handle top-level comments itself. + if in.endRule == 0 { + // Not in a rule. Tell parser about top-level comment. + return _COMMENT + } + + // Otherwise, save comment for later attachment to syntax tree. + if countNL > 1 { + in.comments = append(in.comments, Comment{val.pos, "", false}) + } + in.comments = append(in.comments, Comment{val.pos, val.tok, suffix}) + countNL = 1 + continue + } + + if c == '\\' && len(in.remaining) >= 2 && in.remaining[1] == '\n' { + // We can ignore a trailing \ at end of line. + in.readRune() + continue + } + + // Found non-space non-comment. + break + } + + // Found the beginning of the next token. + in.startToken(val) + defer in.endToken(val) + + // End of file. + if in.eof() { + in.lastToken = "EOF" + return _EOF + } + + // If endRule is 0, we need to recompute where the end + // of the next rule (Python expression) is, so that we can + // generate a virtual end-of-rule semicolon (see above). + if in.endRule == 0 { + in.endRule = len(in.skipPython(in.remaining)) + if in.endRule == 0 { + // skipPython got confused. + // No more virtual semicolons. + in.endRule = -1 + } + } + + // Punctuation tokens. + switch c := in.peekRune(); c { + case '[', '(', '{': + in.depth++ + in.readRune() + return c + + case ']', ')', '}': + in.depth-- + in.readRune() + return c + + case '.', '-', '%', ':', ';', ',', '/', '*': // single-char tokens + in.readRune() + return c + + case '<', '>', '=', '!', '+': // possibly followed by = + in.readRune() + if in.peekRune() == '=' { + in.readRune() + switch c { + case '<': + return _LE + case '>': + return _GE + case '=': + return _EQ + case '!': + return _NE + case '+': + return _ADDEQ + } + } + return c + + case 'r': // possible beginning of raw quoted string + if len(in.remaining) < 2 || in.remaining[1] != '"' && in.remaining[1] != '\'' { + break + } + in.readRune() + c = in.peekRune() + fallthrough + + case '"', '\'': // quoted string + quote := c + if len(in.remaining) >= 3 && in.remaining[0] == byte(quote) && in.remaining[1] == byte(quote) && in.remaining[2] == byte(quote) { + // Triple-quoted string. + in.readRune() + in.readRune() + in.readRune() + var c1, c2, c3 int + for { + if in.eof() { + in.pos = val.pos + in.Error("unexpected EOF in string") + } + c1, c2, c3 = c2, c3, in.readRune() + if c1 == quote && c2 == quote && c3 == quote { + break + } + if c3 == '\\' { + if in.eof() { + in.pos = val.pos + in.Error("unexpected EOF in string") + } + in.readRune() + } + } + } else { + in.readRune() + for { + if in.eof() { + in.pos = val.pos + in.Error("unexpected EOF in string") + } + if in.peekRune() == '\n' { + in.Error("unexpected newline in string") + } + c := in.readRune() + if c == quote { + break + } + if c == '\\' { + if in.eof() { + in.pos = val.pos + in.Error("unexpected EOF in string") + } + in.readRune() + } + } + } + in.endToken(val) + s, triple, err := unquote(val.tok) + if err != nil { + in.Error(fmt.Sprint(err)) + } + val.str = s + val.triple = triple + return _STRING + } + + // Checked all punctuation. Must be identifier token. + if c := in.peekRune(); !isIdent(c) { + in.Error(fmt.Sprintf("unexpected input character %#q", c)) + } + + // Look for raw Python block (class, def, if, etc at beginning of line) and pass through. + if in.depth == 0 && in.pos.LineRune == 1 && hasPythonPrefix(in.remaining) { + // Find end of Python block and advance input beyond it. + // Have to loop calling readRune in order to maintain line number info. + rest := in.skipPython(in.remaining) + for len(in.remaining) > len(rest) { + in.readRune() + } + return _PYTHON + } + + // Scan over alphanumeric identifier. + for { + c := in.peekRune() + if !isIdent(c) { + break + } + in.readRune() + } + + // Call endToken to set val.tok to identifier we just scanned, + // so we can look to see if val.tok is a keyword. + in.endToken(val) + if k := keywordToken[val.tok]; k != 0 { + return k + } + + return _IDENT +} + +// isIdent reports whether c is an identifier rune. +// We treat all non-ASCII runes as identifier runes. +func isIdent(c int) bool { + return '0' <= c && c <= '9' || + 'A' <= c && c <= 'Z' || + 'a' <= c && c <= 'z' || + c == '_' || + c >= 0x80 +} + +// keywordToken records the special tokens for +// strings that should not be treated as ordinary identifiers. +var keywordToken = map[string]int{ + "and": _AND, + "for": _FOR, + "if": _IF, + "else": _ELSE, + "in": _IN, + "is": _IS, + "lambda": _LAMBDA, + "not": _NOT, + "or": _OR, +} + +// Python scanning. +// About 1% of BUILD files embed arbitrary Python into the file. +// We do not attempt to parse it. Instead, we lex just enough to scan +// beyond it, treating the Python block as an unintepreted blob. + +// hasPythonPrefix reports whether p begins with a keyword that would +// introduce an uninterpreted Python block. +func hasPythonPrefix(p []byte) bool { + for _, pre := range prefixes { + if hasPrefixSpace(p, pre) { + return true + } + } + return false +} + +// These keywords introduce uninterpreted Python blocks. +var prefixes = []string{ + "assert", + "class", + "def", + "del", + "for", + "if", + "try", +} + +// hasPrefixSpace reports whether p begins with pre followed by a space or colon. +func hasPrefixSpace(p []byte, pre string) bool { + if len(p) <= len(pre) || p[len(pre)] != ' ' && p[len(pre)] != '\t' && p[len(pre)] != ':' { + return false + } + for i := range pre { + if p[i] != pre[i] { + return false + } + } + return true +} + +func isBlankOrComment(b []byte) bool { + for _, c := range b { + if c == '#' || c == '\n' { + return true + } + if c != ' ' && c != '\t' && c != '\r' { + return false + } + } + return true +} + +// hasPythonContinuation reports whether p begins with a keyword that +// continues an uninterpreted Python block. +func hasPythonContinuation(p []byte) bool { + for _, pre := range continuations { + if hasPrefixSpace(p, pre) { + return true + } + } + return false +} + +// These keywords continue uninterpreted Python blocks. +var continuations = []string{ + "except", + "else", +} + +// skipPython returns the data remaining after the uninterpreted +// Python block beginning at p. It does not advance the input position. +// (The only reason for the input receiver is to be able to call in.Error.) +func (in *input) skipPython(p []byte) []byte { + quote := byte(0) // if non-zero, the kind of quote we're in + tripleQuote := false // if true, the quote is a triple quote + depth := 0 // nesting depth for ( ) [ ] { } + var rest []byte // data after the Python block + + // Scan over input one byte at a time until we find + // an unindented, non-blank, non-comment line + // outside quoted strings and brackets. + for i := 0; i < len(p); i++ { + c := p[i] + if quote != 0 && c == quote && !tripleQuote { + quote = 0 + continue + } + if quote != 0 && c == quote && tripleQuote && i+2 < len(p) && p[i+1] == quote && p[i+2] == quote { + i += 2 + quote = 0 + tripleQuote = false + continue + } + if quote != 0 { + if c == '\\' { + i++ // skip escaped char + } + continue + } + if c == '\'' || c == '"' { + if i+2 < len(p) && p[i+1] == c && p[i+2] == c { + quote = c + tripleQuote = true + i += 2 + continue + } + quote = c + continue + } + + if depth == 0 && i > 0 && p[i-1] == '\n' && (i < 2 || p[i-2] != '\\') { + // Possible stopping point. Save the earliest one we find. + if rest == nil { + rest = p[i:] + } + + if !isBlankOrComment(p[i:]) { + if !hasPythonContinuation(p[i:]) && c != ' ' && c != '\t' { + // Yes, stop here. + break + } + // Not a stopping point after all. + rest = nil + } + } + + switch c { + case '#': + // Skip comment. + for i < len(p) && p[i] != '\n' { + i++ + } + + case '(', '[', '{': + depth++ + + case ')', ']', '}': + depth-- + } + } + if quote != 0 { + in.Error("EOF scanning Python quoted string") + } + return rest +} + +// Comment assignment. +// We build two lists of all subexpressions, preorder and postorder. +// The preorder list is ordered by start location, with outer expressions first. +// The postorder list is ordered by end location, with outer expressions last. +// We use the preorder list to assign each whole-line comment to the syntax +// immediately following it, and we use the postorder list to assign each +// end-of-line comment to the syntax immediately preceding it. + +// order walks the expression adding it and its subexpressions to the +// preorder and postorder lists. +func (in *input) order(v Expr) { + if v != nil { + in.pre = append(in.pre, v) + } + switch v := v.(type) { + default: + panic(fmt.Errorf("order: unexpected type %T", v)) + case nil: + // nothing + case *End: + // nothing + case *File: + for _, stmt := range v.Stmt { + in.order(stmt) + } + case *CommentBlock: + // nothing + case *CallExpr: + in.order(v.X) + for _, x := range v.List { + in.order(x) + } + in.order(&v.End) + case *PythonBlock: + // nothing + case *LiteralExpr: + // nothing + case *StringExpr: + // nothing + case *DotExpr: + in.order(v.X) + case *ListExpr: + for _, x := range v.List { + in.order(x) + } + in.order(&v.End) + case *ListForExpr: + in.order(v.X) + for _, c := range v.For { + in.order(c) + } + for _, c := range v.If { + in.order(c) + } + in.order(&v.End) + case *ForClause: + for _, name := range v.Var { + in.order(name) + } + in.order(v.Expr) + case *IfClause: + in.order(v.Cond) + case *KeyValueExpr: + in.order(v.Key) + in.order(v.Value) + case *DictExpr: + for _, x := range v.List { + in.order(x) + } + in.order(&v.End) + case *TupleExpr: + for _, x := range v.List { + in.order(x) + } + in.order(&v.End) + case *UnaryExpr: + in.order(v.X) + case *BinaryExpr: + in.order(v.X) + in.order(v.Y) + case *ConditionalExpr: + in.order(v.Then) + in.order(v.Test) + in.order(v.Else) + case *ParenExpr: + in.order(v.X) + in.order(&v.End) + case *SliceExpr: + in.order(v.X) + in.order(v.Y) + in.order(v.Z) + case *IndexExpr: + in.order(v.X) + in.order(v.Y) + case *LambdaExpr: + for _, name := range v.Var { + in.order(name) + } + in.order(v.Expr) + } + if v != nil { + in.post = append(in.post, v) + } +} + +// assignComments attaches comments to nearby syntax. +func (in *input) assignComments() { + // Generate preorder and postorder lists. + in.order(in.file) + + // Split into whole-line comments and suffix comments. + var line, suffix []Comment + for _, com := range in.comments { + if com.Suffix { + suffix = append(suffix, com) + } else { + line = append(line, com) + } + } + + // Assign line comments to syntax immediately following. + for _, x := range in.pre { + start, _ := x.Span() + xcom := x.Comment() + for len(line) > 0 && start.Byte >= line[0].Start.Byte { + xcom.Before = append(xcom.Before, line[0]) + line = line[1:] + } + } + + // Remaining line comments go at end of file. + in.file.After = append(in.file.After, line...) + + // Assign suffix comments to syntax immediately before. + for i := len(in.post) - 1; i >= 0; i-- { + x := in.post[i] + + // Do not assign suffix comments to call, list, end-of-list, + // whole file, or conditional expression. + // Instead assign them to the last argument, element, or rule. + switch x.(type) { + case *CallExpr, *ListExpr, *End, *File, *ConditionalExpr: + continue + } + + // Do not assign suffix comments to something that starts + // on an earlier line, so that in + // + // tags = [ "a", + // "b" ], # comment + // + // we assign the comment to "b" and not to tags = [ ... ]. + start, end := x.Span() + if start.Line != end.Line { + continue + } + xcom := x.Comment() + for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte { + xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1]) + suffix = suffix[:len(suffix)-1] + } + } + + // We assigned suffix comments in reverse. + // If multiple suffix comments were appended to the same + // expression node, they are now in reverse. Fix that. + for _, x := range in.post { + reverseComments(x.Comment().Suffix) + } + + // Remaining suffix comments go at beginning of file. + in.file.Before = append(in.file.Before, suffix...) +} + +// reverseComments reverses the []Comment list. +func reverseComments(list []Comment) { + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y new file mode 100644 index 0000000000000000000000000000000000000000..5a2a785602aabf0454a44df8b755741c196eba4c --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y @@ -0,0 +1,667 @@ +// BUILD file parser. + +// This is a yacc grammar. Its lexer is in lex.go. +// +// For a good introduction to writing yacc grammars, see +// Kernighan and Pike's book The Unix Programming Environment. +// +// The definitive yacc manual is +// Stephen C. Johnson and Ravi Sethi, "Yacc: A Parser Generator", +// online at http://plan9.bell-labs.com/sys/doc/yacc.pdf. + +%{ +package build +%} + +// The generated parser puts these fields in a struct named yySymType. +// (The name %union is historical, but it is inaccurate for Go.) +%union { + // input tokens + tok string // raw input syntax + str string // decoding of quoted string + pos Position // position of token + triple bool // was string triple quoted? + + // partial syntax trees + expr Expr + exprs []Expr + forc *ForClause + fors []*ForClause + ifs []*IfClause + string *StringExpr + strings []*StringExpr + + // supporting information + comma Position // position of trailing comma in list, if present + lastRule Expr // most recent rule, to attach line comments to +} + +// These declarations set the type for a $ reference ($$, $1, $2, ...) +// based on the kind of symbol it refers to. Other fields can be referred +// to explicitly, as in $<tok>1. +// +// %token is for input tokens generated by the lexer. +// %type is for higher-level grammar rules defined here. +// +// It is possible to put multiple tokens per line, but it is easier to +// keep ordered using a sparser one-per-line list. + +%token <pos> '%' +%token <pos> '(' +%token <pos> ')' +%token <pos> '*' +%token <pos> '+' +%token <pos> ',' +%token <pos> '-' +%token <pos> '.' +%token <pos> '/' +%token <pos> ':' +%token <pos> '<' +%token <pos> '=' +%token <pos> '>' +%token <pos> '[' +%token <pos> ']' +%token <pos> '{' +%token <pos> '}' + +// By convention, yacc token names are all caps. +// However, we do not want to export them from the Go package +// we are creating, so prefix them all with underscores. + +%token <pos> _ADDEQ // operator += +%token <pos> _AND // keyword and +%token <pos> _COMMENT // top-level # comment +%token <pos> _EOF // end of file +%token <pos> _EQ // operator == +%token <pos> _FOR // keyword for +%token <pos> _GE // operator >= +%token <pos> _IDENT // non-keyword identifier or number +%token <pos> _IF // keyword if +%token <pos> _ELSE // keyword else +%token <pos> _IN // keyword in +%token <pos> _IS // keyword is +%token <pos> _LAMBDA // keyword lambda +%token <pos> _LE // operator <= +%token <pos> _NE // operator != +%token <pos> _NOT // keyword not +%token <pos> _OR // keyword or +%token <pos> _PYTHON // uninterpreted Python block +%token <pos> _STRING // quoted string + +%type <pos> comma_opt +%type <expr> expr +%type <expr> expr_opt +%type <exprs> exprs +%type <exprs> exprs_opt +%type <forc> for_clause +%type <fors> for_clauses +%type <expr> ident +%type <exprs> idents +%type <ifs> if_clauses_opt +%type <exprs> stmts +%type <expr> stmt +%type <expr> keyvalue +%type <exprs> keyvalues +%type <exprs> keyvalues_opt +%type <string> string +%type <strings> strings + +// Operator precedence. +// Operators listed lower in the table bind tighter. + +// We tag rules with this fake, low precedence to indicate +// that when the rule is involved in a shift/reduce +// conflict, we prefer that the parser shift (try for a longer parse). +// Shifting is the default resolution anyway, but stating it explicitly +// silences yacc's warning for that specific case. +%left ShiftInstead + +%left '\n' +%left _ASSERT +// '=' and '+=' have the lowest precedence +// e.g. "x = a if c > 0 else 'bar'" +// followed by +// 'if' and 'else' which have lower precedence than all other operators. +// e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo' +// and not a tuple of "(a, (b if ... ))" +%left '=' _ADDEQ +%left _IF _ELSE +%left ',' +%left ':' +%left _IN _NOT _IS +%left _OR +%left _AND +%left '<' '>' _EQ _NE _LE _GE +%left '+' '-' +%left '*' '/' '%' +%left '.' '[' '(' +%right _UNARY +%left _STRING + +%% + +// Grammar rules. +// +// A note on names: if foo is a rule, then foos is a sequence of foos +// (with interleaved commas or other syntax as appropriate) +// and foo_opt is an optional foo. + +file: + stmts _EOF + { + yylex.(*input).file = &File{Stmt: $1} + return 0 + } + +stmts: + { + $$ = nil + $<lastRule>$ = nil + } +| stmts stmt comma_opt semi_opt + { + // If this statement follows a comment block, + // attach the comments to the statement. + if cb, ok := $<lastRule>1.(*CommentBlock); ok { + $$ = $1 + $$[len($1)-1] = $2 + $2.Comment().Before = cb.After + $<lastRule>$ = $2 + break + } + + // Otherwise add to list. + $$ = append($1, $2) + $<lastRule>$ = $2 + + // Consider this input: + // + // foo() + // # bar + // baz() + // + // If we've just parsed baz(), the # bar is attached to + // foo() as an After comment. Make it a Before comment + // for baz() instead. + if x := $<lastRule>1; x != nil { + com := x.Comment() + $2.Comment().Before = com.After + com.After = nil + } + } +| stmts '\n' + { + // Blank line; sever last rule from future comments. + $$ = $1 + $<lastRule>$ = nil + } +| stmts _COMMENT + { + $$ = $1 + $<lastRule>$ = $<lastRule>1 + if $<lastRule>$ == nil { + cb := &CommentBlock{Start: $2} + $$ = append($$, cb) + $<lastRule>$ = cb + } + com := $<lastRule>$.Comment() + com.After = append(com.After, Comment{Start: $2, Token: $<tok>2}) + } + +stmt: + expr %prec ShiftInstead +| _PYTHON + { + $$ = &PythonBlock{Start: $1, Token: $<tok>1} + } + +semi_opt: +| semi_opt ';' + +expr: + ident +| strings %prec ShiftInstead + { + if len($1) == 1 { + $$ = $1[0] + break + } + + $$ = $1[0] + for _, x := range $1[1:] { + _, end := $$.Span() + $$ = binary($$, end, "+", x) + } + } +| '[' exprs_opt ']' + { + $$ = &ListExpr{ + Start: $1, + List: $2, + Comma: $<comma>2, + End: End{Pos: $3}, + ForceMultiLine: forceMultiLine($1, $2, $3), + } + } +| '[' expr for_clauses if_clauses_opt ']' + { + exprStart, _ := $2.Span() + $$ = &ListForExpr{ + Brack: "[]", + Start: $1, + X: $2, + For: $3, + If: $4, + End: End{Pos: $5}, + ForceMultiLine: $1.Line != exprStart.Line, + } + } +| '(' expr for_clauses if_clauses_opt ')' + { + exprStart, _ := $2.Span() + $$ = &ListForExpr{ + Brack: "()", + Start: $1, + X: $2, + For: $3, + If: $4, + End: End{Pos: $5}, + ForceMultiLine: $1.Line != exprStart.Line, + } + } +| '{' keyvalue for_clauses if_clauses_opt '}' + { + exprStart, _ := $2.Span() + $$ = &ListForExpr{ + Brack: "{}", + Start: $1, + X: $2, + For: $3, + If: $4, + End: End{Pos: $5}, + ForceMultiLine: $1.Line != exprStart.Line, + } + } +| '{' keyvalues_opt '}' + { + $$ = &DictExpr{ + Start: $1, + List: $2, + Comma: $<comma>2, + End: End{Pos: $3}, + ForceMultiLine: forceMultiLine($1, $2, $3), + } + } +| '(' exprs_opt ')' + { + if len($2) == 1 && $<comma>2.Line == 0 { + // Just a parenthesized expression, not a tuple. + $$ = &ParenExpr{ + Start: $1, + X: $2[0], + End: End{Pos: $3}, + ForceMultiLine: forceMultiLine($1, $2, $3), + } + } else { + $$ = &TupleExpr{ + Start: $1, + List: $2, + Comma: $<comma>2, + End: End{Pos: $3}, + ForceCompact: forceCompact($1, $2, $3), + ForceMultiLine: forceMultiLine($1, $2, $3), + } + } + } +| expr '.' _IDENT + { + $$ = &DotExpr{ + X: $1, + Dot: $2, + NamePos: $3, + Name: $<tok>3, + } + } +| expr '(' exprs_opt ')' + { + $$ = &CallExpr{ + X: $1, + ListStart: $2, + List: $3, + End: End{Pos: $4}, + ForceCompact: forceCompact($2, $3, $4), + ForceMultiLine: forceMultiLine($2, $3, $4), + } + } +| expr '(' expr for_clauses if_clauses_opt ')' + { + $$ = &CallExpr{ + X: $1, + ListStart: $2, + List: []Expr{ + &ListForExpr{ + Brack: "", + Start: $2, + X: $3, + For: $4, + If: $5, + End: End{Pos: $6}, + }, + }, + End: End{Pos: $6}, + } + } +| expr '[' expr ']' + { + $$ = &IndexExpr{ + X: $1, + IndexStart: $2, + Y: $3, + End: $4, + } + } +| expr '[' expr_opt ':' expr_opt ']' + { + $$ = &SliceExpr{ + X: $1, + SliceStart: $2, + Y: $3, + Colon: $4, + Z: $5, + End: $6, + } + } +| _LAMBDA exprs ':' expr + { + $$ = &LambdaExpr{ + Lambda: $1, + Var: $2, + Colon: $3, + Expr: $4, + } + } +| '-' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) } +| _NOT expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) } +| '*' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) } +| expr '*' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '%' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '/' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '+' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '-' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '<' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '>' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _EQ expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _LE expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _NE expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _GE expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _ADDEQ expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _IN expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _NOT _IN expr { $$ = binary($1, $2, "not in", $4) } +| expr _OR expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _AND expr { $$ = binary($1, $2, $<tok>2, $3) } +| expr _IS expr + { + if b, ok := $3.(*UnaryExpr); ok && b.Op == "not" { + $$ = binary($1, $2, "is not", b.X) + } else { + $$ = binary($1, $2, $<tok>2, $3) + } + } +| expr _IF expr _ELSE expr + { + $$ = &ConditionalExpr{ + Then: $1, + IfStart: $2, + Test: $3, + ElseStart: $4, + Else: $5, + } + } + +expr_opt: + { + $$ = nil + } +| expr + +// comma_opt is an optional comma. If the comma is present, +// the rule's value is the position of the comma. Otherwise +// the rule's value is the zero position. Tracking this +// lets us distinguish (x) and (x,). +comma_opt: + { + $$ = Position{} + } +| ',' + +keyvalue: + expr ':' expr { + $$ = &KeyValueExpr{ + Key: $1, + Colon: $2, + Value: $3, + } + } + +keyvalues: + keyvalue + { + $$ = []Expr{$1} + } +| keyvalues ',' keyvalue + { + $$ = append($1, $3) + } + +keyvalues_opt: + { + $$, $<comma>$ = nil, Position{} + } +| keyvalues comma_opt + { + $$, $<comma>$ = $1, $2 + } + +exprs: + expr + { + $$ = []Expr{$1} + } +| exprs ',' expr + { + $$ = append($1, $3) + } + +exprs_opt: + { + $$, $<comma>$ = nil, Position{} + } +| exprs comma_opt + { + $$, $<comma>$ = $1, $2 + } + +string: + _STRING + { + $$ = &StringExpr{ + Start: $1, + Value: $<str>1, + TripleQuote: $<triple>1, + End: $1.add($<tok>1), + Token: $<tok>1, + } + } + +strings: + string + { + $$ = []*StringExpr{$1} + } +| strings string + { + $$ = append($1, $2) + } + +ident: + _IDENT + { + $$ = &LiteralExpr{Start: $1, Token: $<tok>1} + } + +idents: + ident + { + $$ = []Expr{$1} + } +| idents ',' ident + { + $$ = append($1, $3) + } + +for_clause: + _FOR idents _IN expr + { + $$ = &ForClause{ + For: $1, + Var: $2, + In: $3, + Expr: $4, + } + } +| _FOR '(' idents ')' _IN expr + { + $$ = &ForClause{ + For: $1, + Var: $3, + In: $5, + Expr: $6, + } + } + +for_clauses: + for_clause + { + $$ = []*ForClause{$1} + } +| for_clauses for_clause { + $$ = append($1, $2) + } + +if_clauses_opt: + { + $$ = nil + } +| if_clauses_opt _IF expr + { + $$ = append($1, &IfClause{ + If: $2, + Cond: $3, + }) + } + +%% + +// Go helper code. + +// unary returns a unary expression with the given +// position, operator, and subexpression. +func unary(pos Position, op string, x Expr) Expr { + return &UnaryExpr{ + OpStart: pos, + Op: op, + X: x, + } +} + +// binary returns a binary expression with the given +// operands, position, and operator. +func binary(x Expr, pos Position, op string, y Expr) Expr { + _, xend := x.Span() + ystart, _ := y.Span() + return &BinaryExpr{ + X: x, + OpStart: pos, + Op: op, + LineBreak: xend.Line < ystart.Line, + Y: y, + } +} + +// forceCompact returns the setting for the ForceCompact field for a call or tuple. +// +// NOTE 1: The field is called ForceCompact, not ForceSingleLine, +// because it only affects the formatting associated with the call or tuple syntax, +// not the formatting of the arguments. For example: +// +// call([ +// 1, +// 2, +// 3, +// ]) +// +// is still a compact call even though it runs on multiple lines. +// +// In contrast the multiline form puts a linebreak after the (. +// +// call( +// [ +// 1, +// 2, +// 3, +// ], +// ) +// +// NOTE 2: Because of NOTE 1, we cannot use start and end on the +// same line as a signal for compact mode: the formatting of an +// embedded list might move the end to a different line, which would +// then look different on rereading and cause buildifier not to be +// idempotent. Instead, we have to look at properties guaranteed +// to be preserved by the reformatting, namely that the opening +// paren and the first expression are on the same line and that +// each subsequent expression begins on the same line as the last +// one ended (no line breaks after comma). +func forceCompact(start Position, list []Expr, end Position) bool { + if len(list) <= 1 { + // The call or tuple will probably be compact anyway; don't force it. + return false + } + + // If there are any named arguments or non-string, non-literal + // arguments, cannot force compact mode. + line := start.Line + for _, x := range list { + start, end := x.Span() + if start.Line != line { + return false + } + line = end.Line + switch x.(type) { + case *LiteralExpr, *StringExpr: + // ok + default: + return false + } + } + return end.Line == line +} + +// forceMultiLine returns the setting for the ForceMultiLine field. +func forceMultiLine(start Position, list []Expr, end Position) bool { + if len(list) > 1 { + // The call will be multiline anyway, because it has multiple elements. Don't force it. + return false + } + + if len(list) == 0 { + // Empty list: use position of brackets. + return start.Line != end.Line + } + + // Single-element list. + // Check whether opening bracket is on different line than beginning of + // element, or closing bracket is on different line than end of element. + elemStart, elemEnd := list[0].Span() + return start.Line != elemStart.Line || end.Line != elemEnd.Line +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y.go new file mode 100644 index 0000000000000000000000000000000000000000..638c74c629124f40d7a554cab392c46dce05a622 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/parse.y.go @@ -0,0 +1,1290 @@ +//line parse.y:13 +package build + +import __yyfmt__ "fmt" + +//line parse.y:13 +//line parse.y:18 +type yySymType struct { + yys int + // input tokens + tok string // raw input syntax + str string // decoding of quoted string + pos Position // position of token + triple bool // was string triple quoted? + + // partial syntax trees + expr Expr + exprs []Expr + forc *ForClause + fors []*ForClause + ifs []*IfClause + string *StringExpr + strings []*StringExpr + + // supporting information + comma Position // position of trailing comma in list, if present + lastRule Expr // most recent rule, to attach line comments to +} + +const _ADDEQ = 57346 +const _AND = 57347 +const _COMMENT = 57348 +const _EOF = 57349 +const _EQ = 57350 +const _FOR = 57351 +const _GE = 57352 +const _IDENT = 57353 +const _IF = 57354 +const _ELSE = 57355 +const _IN = 57356 +const _IS = 57357 +const _LAMBDA = 57358 +const _LE = 57359 +const _NE = 57360 +const _NOT = 57361 +const _OR = 57362 +const _PYTHON = 57363 +const _STRING = 57364 +const ShiftInstead = 57365 +const _ASSERT = 57366 +const _UNARY = 57367 + +var yyToknames = [...]string{ + "$end", + "error", + "$unk", + "'%'", + "'('", + "')'", + "'*'", + "'+'", + "','", + "'-'", + "'.'", + "'/'", + "':'", + "'<'", + "'='", + "'>'", + "'['", + "']'", + "'{'", + "'}'", + "_ADDEQ", + "_AND", + "_COMMENT", + "_EOF", + "_EQ", + "_FOR", + "_GE", + "_IDENT", + "_IF", + "_ELSE", + "_IN", + "_IS", + "_LAMBDA", + "_LE", + "_NE", + "_NOT", + "_OR", + "_PYTHON", + "_STRING", + "ShiftInstead", + "'\\n'", + "_ASSERT", + "_UNARY", + "';'", +} +var yyStatenames = [...]string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyInitialStackSize = 16 + +//line parse.y:564 + +// Go helper code. + +// unary returns a unary expression with the given +// position, operator, and subexpression. +func unary(pos Position, op string, x Expr) Expr { + return &UnaryExpr{ + OpStart: pos, + Op: op, + X: x, + } +} + +// binary returns a binary expression with the given +// operands, position, and operator. +func binary(x Expr, pos Position, op string, y Expr) Expr { + _, xend := x.Span() + ystart, _ := y.Span() + return &BinaryExpr{ + X: x, + OpStart: pos, + Op: op, + LineBreak: xend.Line < ystart.Line, + Y: y, + } +} + +// forceCompact returns the setting for the ForceCompact field for a call or tuple. +// +// NOTE 1: The field is called ForceCompact, not ForceSingleLine, +// because it only affects the formatting associated with the call or tuple syntax, +// not the formatting of the arguments. For example: +// +// call([ +// 1, +// 2, +// 3, +// ]) +// +// is still a compact call even though it runs on multiple lines. +// +// In contrast the multiline form puts a linebreak after the (. +// +// call( +// [ +// 1, +// 2, +// 3, +// ], +// ) +// +// NOTE 2: Because of NOTE 1, we cannot use start and end on the +// same line as a signal for compact mode: the formatting of an +// embedded list might move the end to a different line, which would +// then look different on rereading and cause buildifier not to be +// idempotent. Instead, we have to look at properties guaranteed +// to be preserved by the reformatting, namely that the opening +// paren and the first expression are on the same line and that +// each subsequent expression begins on the same line as the last +// one ended (no line breaks after comma). +func forceCompact(start Position, list []Expr, end Position) bool { + if len(list) <= 1 { + // The call or tuple will probably be compact anyway; don't force it. + return false + } + + // If there are any named arguments or non-string, non-literal + // arguments, cannot force compact mode. + line := start.Line + for _, x := range list { + start, end := x.Span() + if start.Line != line { + return false + } + line = end.Line + switch x.(type) { + case *LiteralExpr, *StringExpr: + // ok + default: + return false + } + } + return end.Line == line +} + +// forceMultiLine returns the setting for the ForceMultiLine field. +func forceMultiLine(start Position, list []Expr, end Position) bool { + if len(list) > 1 { + // The call will be multiline anyway, because it has multiple elements. Don't force it. + return false + } + + if len(list) == 0 { + // Empty list: use position of brackets. + return start.Line != end.Line + } + + // Single-element list. + // Check whether opening bracket is on different line than beginning of + // element, or closing bracket is on different line than end of element. + elemStart, elemEnd := list[0].Span() + return start.Line != elemStart.Line || end.Line != elemEnd.Line +} + +//line yacctab:1 +var yyExca = [...]int{ + -1, 1, + 1, -1, + -2, 0, +} + +const yyNprod = 71 +const yyPrivate = 57344 + +var yyTokenNames []string +var yyStates []string + +const yyLast = 542 + +var yyAct = [...]int{ + + 53, 109, 9, 7, 65, 51, 86, 100, 21, 20, + 135, 80, 47, 49, 87, 56, 57, 58, 59, 124, + 18, 61, 107, 129, 127, 63, 64, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + 79, 125, 81, 82, 83, 84, 123, 123, 12, 110, + 17, 88, 128, 15, 122, 46, 91, 90, 93, 94, + 11, 123, 13, 97, 130, 123, 85, 104, 50, 48, + 102, 18, 18, 96, 99, 89, 14, 24, 98, 16, + 62, 105, 20, 23, 55, 27, 24, 22, 26, 25, + 112, 111, 23, 28, 134, 101, 115, 124, 25, 117, + 112, 108, 116, 92, 19, 120, 108, 121, 108, 119, + 60, 1, 126, 111, 113, 45, 114, 108, 10, 52, + 54, 4, 2, 0, 131, 118, 133, 132, 0, 0, + 0, 27, 24, 0, 26, 29, 136, 30, 23, 28, + 0, 31, 37, 32, 25, 0, 0, 0, 38, 42, + 0, 0, 33, 0, 36, 0, 44, 106, 39, 43, + 0, 34, 35, 40, 41, 27, 24, 0, 26, 29, + 0, 30, 23, 28, 0, 31, 37, 32, 25, 103, + 0, 0, 38, 42, 0, 0, 33, 0, 36, 0, + 44, 0, 39, 43, 0, 34, 35, 40, 41, 27, + 24, 0, 26, 29, 0, 30, 23, 28, 0, 31, + 37, 32, 25, 0, 0, 0, 38, 42, 0, 0, + 33, 88, 36, 0, 44, 0, 39, 43, 0, 34, + 35, 40, 41, 27, 24, 0, 26, 29, 0, 30, + 23, 28, 95, 31, 37, 32, 25, 0, 0, 0, + 38, 42, 0, 0, 33, 0, 36, 0, 44, 0, + 39, 43, 0, 34, 35, 40, 41, 27, 24, 0, + 26, 29, 0, 30, 23, 28, 0, 31, 37, 32, + 25, 0, 0, 0, 38, 42, 0, 0, 33, 0, + 36, 0, 44, 0, 39, 43, 0, 34, 35, 40, + 41, 27, 24, 0, 26, 29, 0, 30, 23, 28, + 0, 31, 37, 32, 25, 0, 0, 0, 38, 42, + 0, 0, 33, 0, 36, 0, 0, 0, 39, 43, + 0, 34, 35, 40, 41, 27, 24, 0, 26, 29, + 0, 30, 23, 28, 0, 31, 0, 32, 25, 0, + 0, 0, 0, 42, 0, 0, 33, 0, 36, 0, + 44, 0, 39, 43, 0, 34, 35, 40, 41, 27, + 24, 0, 26, 29, 0, 30, 23, 28, 0, 31, + 0, 32, 25, 0, 0, 0, 0, 42, 0, 0, + 33, 0, 36, 12, 0, 17, 39, 43, 15, 34, + 35, 40, 41, 0, 0, 11, 0, 13, 0, 0, + 0, 6, 3, 0, 0, 0, 18, 0, 0, 0, + 0, 14, 0, 0, 16, 0, 8, 20, 0, 5, + 27, 24, 0, 26, 29, 0, 30, 23, 28, 0, + 31, 0, 32, 25, 0, 0, 0, 0, 42, 0, + 0, 33, 0, 36, 0, 0, 0, 0, 0, 0, + 34, 35, 0, 41, 27, 24, 0, 26, 29, 0, + 30, 23, 28, 0, 31, 0, 32, 25, 0, 0, + 0, 0, 42, 0, 0, 33, 0, 36, 0, 0, + 0, 0, 0, 0, 34, 35, 27, 24, 0, 26, + 29, 0, 30, 23, 28, 0, 31, 0, 32, 25, + 0, 0, 0, 0, 0, 0, 0, 33, 0, 36, + 0, 0, 0, 0, 0, 0, 34, 35, 27, 24, + 0, 26, 29, 0, 30, 23, 28, 0, 0, 0, + 0, 25, +} +var yyPact = [...]int{ + + -1000, -1000, 388, -1000, 78, -1000, -1000, 263, -1000, -1000, + -30, 43, 43, 43, 43, 43, 43, 43, -1000, -1000, + -1000, -1000, -1000, -7, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + -20, 43, 43, 43, 43, -1000, 48, 195, 66, 195, + 97, 25, 39, 229, 64, 65, 263, -1000, -1000, -1000, + -37, -1000, 89, 195, 161, 54, 72, 72, 72, 81, + 81, 524, 524, 524, 524, 524, 524, 331, 331, 426, + 43, 460, 492, 426, 127, -1000, 25, -1000, 44, 43, + -1000, 25, -1000, 25, -1000, 43, 43, -1000, 43, 43, + -1000, -1000, 25, -1000, 43, 426, 43, 36, -1000, 10, + -8, -1000, 263, 18, 32, 263, -1000, 365, 17, 46, + 263, 365, -1000, 43, -8, 43, 88, -1000, -1000, -1000, + -1000, 297, -1000, 297, -21, 43, 297, +} +var yyPgo = [...]int{ + + 0, 8, 0, 4, 69, 55, 14, 6, 2, 1, + 22, 122, 121, 5, 120, 119, 104, 118, 111, 110, +} +var yyR1 = [...]int{ + + 0, 18, 11, 11, 11, 11, 12, 12, 19, 19, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 3, 1, 1, + 13, 14, 14, 15, 15, 4, 4, 5, 5, 16, + 17, 17, 8, 9, 9, 6, 6, 7, 7, 10, + 10, +} +var yyR2 = [...]int{ + + 0, 2, 0, 4, 2, 2, 1, 1, 0, 2, + 1, 1, 3, 5, 5, 5, 3, 3, 3, 4, + 6, 4, 6, 4, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 4, 3, 3, 3, 5, 0, 1, 0, 1, + 3, 1, 3, 0, 2, 1, 3, 0, 2, 1, + 1, 2, 1, 1, 3, 4, 6, 1, 2, 0, + 3, +} +var yyChk = [...]int{ + + -1000, -18, -11, 24, -12, 41, 23, -2, 38, -8, + -17, 17, 5, 19, 33, 10, 36, 7, 28, -16, + 39, -1, 9, 11, 5, 17, 7, 4, 12, 8, + 10, 14, 16, 25, 34, 35, 27, 15, 21, 31, + 36, 37, 22, 32, 29, -16, -5, -2, -4, -2, + -5, -13, -15, -2, -14, -4, -2, -2, -2, -2, + -19, 28, -5, -2, -2, -3, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + 31, -2, -2, -2, -2, 18, -7, -6, 26, 9, + -1, -7, 6, -7, 20, 13, 9, -1, 13, 9, + 44, 6, -7, 18, 13, -2, 30, -10, -6, -9, + 5, -8, -2, -10, -10, -2, -13, -2, -10, -3, + -2, -2, 18, 29, 9, 31, -9, 6, 20, 6, + 18, -2, -8, -2, 6, 31, -2, +} +var yyDef = [...]int{ + + 2, -2, 0, 1, 48, 4, 5, 6, 7, 10, + 11, 57, 57, 53, 0, 0, 0, 0, 62, 60, + 59, 8, 49, 0, 57, 46, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 61, 0, 55, 48, 55, + 0, 51, 0, 0, 48, 0, 55, 24, 25, 26, + 3, 18, 0, 55, 47, 0, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 0, 42, 43, 44, 0, 12, 69, 67, 0, 49, + 58, 69, 17, 69, 16, 0, 49, 54, 0, 0, + 9, 19, 69, 21, 46, 41, 0, 0, 68, 0, + 0, 63, 56, 0, 0, 50, 52, 23, 0, 0, + 47, 45, 13, 0, 0, 0, 0, 14, 15, 20, + 22, 70, 64, 65, 0, 0, 66, +} +var yyTok1 = [...]int{ + + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 41, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, + 5, 6, 7, 8, 9, 10, 11, 12, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 13, 44, + 14, 15, 16, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 17, 3, 18, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 19, 3, 20, +} +var yyTok2 = [...]int{ + + 2, 3, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 42, 43, +} +var yyTok3 = [...]int{ + 0, +} + +var yyErrorMessages = [...]struct { + state int + token int + msg string +}{} + +//line yaccpar:1 + +/* parser for yacc output */ + +var ( + yyDebug = 0 + yyErrorVerbose = false +) + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +type yyParser interface { + Parse(yyLexer) int + Lookahead() int +} + +type yyParserImpl struct { + lval yySymType + stack [yyInitialStackSize]yySymType + char int +} + +func (p *yyParserImpl) Lookahead() int { + return p.char +} + +func yyNewParser() yyParser { + return &yyParserImpl{} +} + +const yyFlag = -1000 + +func yyTokname(c int) string { + if c >= 1 && c-1 < len(yyToknames) { + if yyToknames[c-1] != "" { + return yyToknames[c-1] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yyErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !yyErrorVerbose { + return "syntax error" + } + + for _, e := range yyErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + yyTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := yyPact[state] + for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { + if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if yyDef[state] == -2 { + i := 0 + for yyExca[i] != -1 || yyExca[i+1] != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; yyExca[i] >= 0; i += 2 { + tok := yyExca[i] + if tok < TOKSTART || yyExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if yyExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += yyTokname(tok) + } + return res +} + +func yylex1(lex yyLexer, lval *yySymType) (char, token int) { + token = 0 + char = lex.Lex(lval) + if char <= 0 { + token = yyTok1[0] + goto out + } + if char < len(yyTok1) { + token = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + token = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + token = yyTok3[i+0] + if token == char { + token = yyTok3[i+1] + goto out + } + } + +out: + if token == 0 { + token = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) + } + return char, token +} + +func yyParse(yylex yyLexer) int { + return yyNewParser().Parse(yylex) +} + +func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { + var yyn int + var yyVAL yySymType + var yyDollar []yySymType + _ = yyDollar // silence set and not used + yyS := yyrcvr.stack[:] + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yyrcvr.char = -1 + yytoken := -1 // yyrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + yystate = -1 + yyrcvr.char = -1 + yytoken = -1 + }() + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + yyn += yytoken + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yytoken { /* valid shift */ + yyrcvr.char = -1 + yytoken = -1 + yyVAL = yyrcvr.lval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yytoken { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error(yyErrorMessage(yystate, yytoken)) + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) + } + if yytoken == yyEofCode { + goto ret1 + } + yyrcvr.char = -1 + yytoken = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + // yyp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if yyp+1 >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:151 + { + yylex.(*input).file = &File{Stmt: yyDollar[1].exprs} + return 0 + } + case 2: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:157 + { + yyVAL.exprs = nil + yyVAL.lastRule = nil + } + case 3: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:162 + { + // If this statement follows a comment block, + // attach the comments to the statement. + if cb, ok := yyDollar[1].lastRule.(*CommentBlock); ok { + yyVAL.exprs = yyDollar[1].exprs + yyVAL.exprs[len(yyDollar[1].exprs)-1] = yyDollar[2].expr + yyDollar[2].expr.Comment().Before = cb.After + yyVAL.lastRule = yyDollar[2].expr + break + } + + // Otherwise add to list. + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[2].expr) + yyVAL.lastRule = yyDollar[2].expr + + // Consider this input: + // + // foo() + // # bar + // baz() + // + // If we've just parsed baz(), the # bar is attached to + // foo() as an After comment. Make it a Before comment + // for baz() instead. + if x := yyDollar[1].lastRule; x != nil { + com := x.Comment() + yyDollar[2].expr.Comment().Before = com.After + com.After = nil + } + } + case 4: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:193 + { + // Blank line; sever last rule from future comments. + yyVAL.exprs = yyDollar[1].exprs + yyVAL.lastRule = nil + } + case 5: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:199 + { + yyVAL.exprs = yyDollar[1].exprs + yyVAL.lastRule = yyDollar[1].lastRule + if yyVAL.lastRule == nil { + cb := &CommentBlock{Start: yyDollar[2].pos} + yyVAL.exprs = append(yyVAL.exprs, cb) + yyVAL.lastRule = cb + } + com := yyVAL.lastRule.Comment() + com.After = append(com.After, Comment{Start: yyDollar[2].pos, Token: yyDollar[2].tok}) + } + case 7: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:214 + { + yyVAL.expr = &PythonBlock{Start: yyDollar[1].pos, Token: yyDollar[1].tok} + } + case 11: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:224 + { + if len(yyDollar[1].strings) == 1 { + yyVAL.expr = yyDollar[1].strings[0] + break + } + + yyVAL.expr = yyDollar[1].strings[0] + for _, x := range yyDollar[1].strings[1:] { + _, end := yyVAL.expr.Span() + yyVAL.expr = binary(yyVAL.expr, end, "+", x) + } + } + case 12: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:237 + { + yyVAL.expr = &ListExpr{ + Start: yyDollar[1].pos, + List: yyDollar[2].exprs, + Comma: yyDollar[2].comma, + End: End{Pos: yyDollar[3].pos}, + ForceMultiLine: forceMultiLine(yyDollar[1].pos, yyDollar[2].exprs, yyDollar[3].pos), + } + } + case 13: + yyDollar = yyS[yypt-5 : yypt+1] + //line parse.y:247 + { + exprStart, _ := yyDollar[2].expr.Span() + yyVAL.expr = &ListForExpr{ + Brack: "[]", + Start: yyDollar[1].pos, + X: yyDollar[2].expr, + For: yyDollar[3].fors, + If: yyDollar[4].ifs, + End: End{Pos: yyDollar[5].pos}, + ForceMultiLine: yyDollar[1].pos.Line != exprStart.Line, + } + } + case 14: + yyDollar = yyS[yypt-5 : yypt+1] + //line parse.y:260 + { + exprStart, _ := yyDollar[2].expr.Span() + yyVAL.expr = &ListForExpr{ + Brack: "()", + Start: yyDollar[1].pos, + X: yyDollar[2].expr, + For: yyDollar[3].fors, + If: yyDollar[4].ifs, + End: End{Pos: yyDollar[5].pos}, + ForceMultiLine: yyDollar[1].pos.Line != exprStart.Line, + } + } + case 15: + yyDollar = yyS[yypt-5 : yypt+1] + //line parse.y:273 + { + exprStart, _ := yyDollar[2].expr.Span() + yyVAL.expr = &ListForExpr{ + Brack: "{}", + Start: yyDollar[1].pos, + X: yyDollar[2].expr, + For: yyDollar[3].fors, + If: yyDollar[4].ifs, + End: End{Pos: yyDollar[5].pos}, + ForceMultiLine: yyDollar[1].pos.Line != exprStart.Line, + } + } + case 16: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:286 + { + yyVAL.expr = &DictExpr{ + Start: yyDollar[1].pos, + List: yyDollar[2].exprs, + Comma: yyDollar[2].comma, + End: End{Pos: yyDollar[3].pos}, + ForceMultiLine: forceMultiLine(yyDollar[1].pos, yyDollar[2].exprs, yyDollar[3].pos), + } + } + case 17: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:296 + { + if len(yyDollar[2].exprs) == 1 && yyDollar[2].comma.Line == 0 { + // Just a parenthesized expression, not a tuple. + yyVAL.expr = &ParenExpr{ + Start: yyDollar[1].pos, + X: yyDollar[2].exprs[0], + End: End{Pos: yyDollar[3].pos}, + ForceMultiLine: forceMultiLine(yyDollar[1].pos, yyDollar[2].exprs, yyDollar[3].pos), + } + } else { + yyVAL.expr = &TupleExpr{ + Start: yyDollar[1].pos, + List: yyDollar[2].exprs, + Comma: yyDollar[2].comma, + End: End{Pos: yyDollar[3].pos}, + ForceCompact: forceCompact(yyDollar[1].pos, yyDollar[2].exprs, yyDollar[3].pos), + ForceMultiLine: forceMultiLine(yyDollar[1].pos, yyDollar[2].exprs, yyDollar[3].pos), + } + } + } + case 18: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:317 + { + yyVAL.expr = &DotExpr{ + X: yyDollar[1].expr, + Dot: yyDollar[2].pos, + NamePos: yyDollar[3].pos, + Name: yyDollar[3].tok, + } + } + case 19: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:326 + { + yyVAL.expr = &CallExpr{ + X: yyDollar[1].expr, + ListStart: yyDollar[2].pos, + List: yyDollar[3].exprs, + End: End{Pos: yyDollar[4].pos}, + ForceCompact: forceCompact(yyDollar[2].pos, yyDollar[3].exprs, yyDollar[4].pos), + ForceMultiLine: forceMultiLine(yyDollar[2].pos, yyDollar[3].exprs, yyDollar[4].pos), + } + } + case 20: + yyDollar = yyS[yypt-6 : yypt+1] + //line parse.y:337 + { + yyVAL.expr = &CallExpr{ + X: yyDollar[1].expr, + ListStart: yyDollar[2].pos, + List: []Expr{ + &ListForExpr{ + Brack: "", + Start: yyDollar[2].pos, + X: yyDollar[3].expr, + For: yyDollar[4].fors, + If: yyDollar[5].ifs, + End: End{Pos: yyDollar[6].pos}, + }, + }, + End: End{Pos: yyDollar[6].pos}, + } + } + case 21: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:355 + { + yyVAL.expr = &IndexExpr{ + X: yyDollar[1].expr, + IndexStart: yyDollar[2].pos, + Y: yyDollar[3].expr, + End: yyDollar[4].pos, + } + } + case 22: + yyDollar = yyS[yypt-6 : yypt+1] + //line parse.y:364 + { + yyVAL.expr = &SliceExpr{ + X: yyDollar[1].expr, + SliceStart: yyDollar[2].pos, + Y: yyDollar[3].expr, + Colon: yyDollar[4].pos, + Z: yyDollar[5].expr, + End: yyDollar[6].pos, + } + } + case 23: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:375 + { + yyVAL.expr = &LambdaExpr{ + Lambda: yyDollar[1].pos, + Var: yyDollar[2].exprs, + Colon: yyDollar[3].pos, + Expr: yyDollar[4].expr, + } + } + case 24: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:383 + { + yyVAL.expr = unary(yyDollar[1].pos, yyDollar[1].tok, yyDollar[2].expr) + } + case 25: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:384 + { + yyVAL.expr = unary(yyDollar[1].pos, yyDollar[1].tok, yyDollar[2].expr) + } + case 26: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:385 + { + yyVAL.expr = unary(yyDollar[1].pos, yyDollar[1].tok, yyDollar[2].expr) + } + case 27: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:386 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 28: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:387 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 29: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:388 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 30: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:389 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 31: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:390 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 32: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:391 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 33: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:392 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 34: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:393 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 35: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:394 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 36: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:395 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 37: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:396 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 38: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:397 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 39: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:398 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 40: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:399 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 41: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:400 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, "not in", yyDollar[4].expr) + } + case 42: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:401 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 43: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:402 + { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + case 44: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:404 + { + if b, ok := yyDollar[3].expr.(*UnaryExpr); ok && b.Op == "not" { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, "is not", b.X) + } else { + yyVAL.expr = binary(yyDollar[1].expr, yyDollar[2].pos, yyDollar[2].tok, yyDollar[3].expr) + } + } + case 45: + yyDollar = yyS[yypt-5 : yypt+1] + //line parse.y:412 + { + yyVAL.expr = &ConditionalExpr{ + Then: yyDollar[1].expr, + IfStart: yyDollar[2].pos, + Test: yyDollar[3].expr, + ElseStart: yyDollar[4].pos, + Else: yyDollar[5].expr, + } + } + case 46: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:423 + { + yyVAL.expr = nil + } + case 48: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:433 + { + yyVAL.pos = Position{} + } + case 50: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:439 + { + yyVAL.expr = &KeyValueExpr{ + Key: yyDollar[1].expr, + Colon: yyDollar[2].pos, + Value: yyDollar[3].expr, + } + } + case 51: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:449 + { + yyVAL.exprs = []Expr{yyDollar[1].expr} + } + case 52: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:453 + { + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr) + } + case 53: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:458 + { + yyVAL.exprs, yyVAL.comma = nil, Position{} + } + case 54: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:462 + { + yyVAL.exprs, yyVAL.comma = yyDollar[1].exprs, yyDollar[2].pos + } + case 55: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:468 + { + yyVAL.exprs = []Expr{yyDollar[1].expr} + } + case 56: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:472 + { + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr) + } + case 57: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:477 + { + yyVAL.exprs, yyVAL.comma = nil, Position{} + } + case 58: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:481 + { + yyVAL.exprs, yyVAL.comma = yyDollar[1].exprs, yyDollar[2].pos + } + case 59: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:487 + { + yyVAL.string = &StringExpr{ + Start: yyDollar[1].pos, + Value: yyDollar[1].str, + TripleQuote: yyDollar[1].triple, + End: yyDollar[1].pos.add(yyDollar[1].tok), + Token: yyDollar[1].tok, + } + } + case 60: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:499 + { + yyVAL.strings = []*StringExpr{yyDollar[1].string} + } + case 61: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:503 + { + yyVAL.strings = append(yyDollar[1].strings, yyDollar[2].string) + } + case 62: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:509 + { + yyVAL.expr = &LiteralExpr{Start: yyDollar[1].pos, Token: yyDollar[1].tok} + } + case 63: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:515 + { + yyVAL.exprs = []Expr{yyDollar[1].expr} + } + case 64: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:519 + { + yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr) + } + case 65: + yyDollar = yyS[yypt-4 : yypt+1] + //line parse.y:525 + { + yyVAL.forc = &ForClause{ + For: yyDollar[1].pos, + Var: yyDollar[2].exprs, + In: yyDollar[3].pos, + Expr: yyDollar[4].expr, + } + } + case 66: + yyDollar = yyS[yypt-6 : yypt+1] + //line parse.y:534 + { + yyVAL.forc = &ForClause{ + For: yyDollar[1].pos, + Var: yyDollar[3].exprs, + In: yyDollar[5].pos, + Expr: yyDollar[6].expr, + } + } + case 67: + yyDollar = yyS[yypt-1 : yypt+1] + //line parse.y:545 + { + yyVAL.fors = []*ForClause{yyDollar[1].forc} + } + case 68: + yyDollar = yyS[yypt-2 : yypt+1] + //line parse.y:548 + { + yyVAL.fors = append(yyDollar[1].fors, yyDollar[2].forc) + } + case 69: + yyDollar = yyS[yypt-0 : yypt+1] + //line parse.y:553 + { + yyVAL.ifs = nil + } + case 70: + yyDollar = yyS[yypt-3 : yypt+1] + //line parse.y:557 + { + yyVAL.ifs = append(yyDollar[1].ifs, &IfClause{ + If: yyDollar[2].pos, + Cond: yyDollar[3].expr, + }) + } + } + goto yystack /* stack new state and value */ +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/print.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/print.go new file mode 100644 index 0000000000000000000000000000000000000000..c4b5fda1f7e36456c21d1ab33f83f04b65ac1903 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/print.go @@ -0,0 +1,607 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Printing of syntax trees. + +package build + +import ( + "bytes" + "fmt" + "strings" +) + +// Format returns the formatted form of the given BUILD file. +func Format(f *File) []byte { + pr := &printer{} + pr.file(f) + return pr.Bytes() +} + +// FormatString returns the string form of the given expression. +func FormatString(x Expr) string { + pr := &printer{} + switch x := x.(type) { + case *File: + pr.file(x) + default: + pr.expr(x, precLow) + } + return pr.String() +} + +// A printer collects the state during printing of a file or expression. +type printer struct { + bytes.Buffer // output buffer + comment []Comment // pending end-of-line comments + margin int // left margin (indent), a number of spaces + depth int // nesting depth inside ( ) [ ] { } +} + +// printf prints to the buffer. +func (p *printer) printf(format string, args ...interface{}) { + fmt.Fprintf(p, format, args...) +} + +// indent returns the position on the current line, in bytes, 0-indexed. +func (p *printer) indent() int { + b := p.Bytes() + n := 0 + for n < len(b) && b[len(b)-1-n] != '\n' { + n++ + } + return n +} + +// newline ends the current line, flushing end-of-line comments. +// It must only be called when printing a newline is known to be safe: +// when not inside an expression or when p.depth > 0. +// To break a line inside an expression that might not be enclosed +// in brackets of some kind, use breakline instead. +func (p *printer) newline() { + if len(p.comment) > 0 { + p.printf(" ") + for i, com := range p.comment { + if i > 0 { + p.trim() + p.printf("\n%*s", p.margin, "") + } + p.printf("%s", strings.TrimSpace(com.Token)) + } + p.comment = p.comment[:0] + } + + p.trim() + p.printf("\n%*s", p.margin, "") +} + +// breakline breaks the current line, inserting a continuation \ if needed. +// If no continuation \ is needed, breakline flushes end-of-line comments. +func (p *printer) breakline() { + if p.depth == 0 { + // Cannot have both final \ and comments. + p.printf(" \\\n%*s", p.margin, "") + return + } + + // Safe to use newline. + p.newline() +} + +// trim removes trailing spaces from the current line. +func (p *printer) trim() { + // Remove trailing space from line we're about to end. + b := p.Bytes() + n := len(b) + for n > 0 && b[n-1] == ' ' { + n-- + } + p.Truncate(n) +} + +// file formats the given file into the print buffer. +func (p *printer) file(f *File) { + for _, com := range f.Before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + for i, stmt := range f.Stmt { + switch stmt := stmt.(type) { + case *CommentBlock: + // comments already handled + + case *PythonBlock: + for _, com := range stmt.Before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + p.printf("%s", stmt.Token) // includes trailing newline + + default: + p.expr(stmt, precLow) + p.newline() + } + + for _, com := range stmt.Comment().After { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + if i+1 < len(f.Stmt) && !compactStmt(stmt, f.Stmt[i+1]) { + p.newline() + } + } + + for _, com := range f.After { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } +} + +// compactStmt reports whether the pair of statements s1, s2 +// should be printed without an intervening blank line. +// We omit the blank line when both are subinclude statements +// and the second one has no leading comments. +func compactStmt(s1, s2 Expr) bool { + if len(s2.Comment().Before) > 0 { + return false + } + + return (isCall(s1, "subinclude") || isCall(s1, "load")) && + (isCall(s2, "subinclude") || isCall(s2, "load")) +} + +// isCall reports whether x is a call to a function with the given name. +func isCall(x Expr, name string) bool { + c, ok := x.(*CallExpr) + if !ok { + return false + } + nam, ok := c.X.(*LiteralExpr) + if !ok { + return false + } + return nam.Token == name +} + +// Expression formatting. + +// The expression formatter must introduce parentheses to force the +// meaning described by the parse tree. We preserve parentheses in the +// input, so extra parentheses are only needed if we have edited the tree. +// +// For example consider these expressions: +// (1) "x" "y" % foo +// (2) "x" + "y" % foo +// (3) "x" + ("y" % foo) +// (4) ("x" + "y") % foo +// When we parse (1), we represent the concatenation as an addition. +// However, if we print the addition back out without additional parens, +// as in (2), it has the same meaning as (3), which is not the original +// meaning. To preserve the original meaning we must add parens as in (4). +// +// To allow arbitrary rewrites to be formatted properly, we track full +// operator precedence while printing instead of just handling this one +// case of string concatenation. +// +// The precedences are assigned values low to high. A larger number +// binds tighter than a smaller number. All binary operators bind +// left-to-right. +const ( + precLow = iota + precAssign + precComma + precColon + precIn + precOr + precAnd + precCmp + precAdd + precMultiply + precSuffix + precUnary + precConcat +) + +// opPrec gives the precedence for operators found in a BinaryExpr. +var opPrec = map[string]int{ + "=": precAssign, + "+=": precAssign, + "or": precOr, + "and": precAnd, + "<": precCmp, + ">": precCmp, + "==": precCmp, + "!=": precCmp, + "<=": precCmp, + ">=": precCmp, + "+": precAdd, + "-": precAdd, + "*": precMultiply, + "/": precMultiply, + "%": precMultiply, +} + +// expr prints the expression v to the print buffer. +// The value outerPrec gives the precedence of the operator +// outside expr. If that operator binds tighter than v's operator, +// expr must introduce parentheses to preserve the meaning +// of the parse tree (see above). +func (p *printer) expr(v Expr, outerPrec int) { + // Emit line-comments preceding this expression. + // If we are in the middle of an expression but not inside ( ) [ ] { } + // then we cannot just break the line: we'd have to end it with a \. + // However, even then we can't emit line comments since that would + // end the expression. This is only a concern if we have rewritten + // the parse tree. If comments were okay before this expression in + // the original input they're still okay now, in the absense of rewrites. + // + // TODO(bazel-team): Check whether it is valid to emit comments right now, + // and if not, insert them earlier in the output instead, at the most + // recent \n not following a \ line. + if before := v.Comment().Before; len(before) > 0 { + // Want to print a line comment. + // Line comments must be at the current margin. + p.trim() + if p.indent() > 0 { + // There's other text on the line. Start a new line. + p.printf("\n") + } + // Re-indent to margin. + p.printf("%*s", p.margin, "") + for _, com := range before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + } + + // Do we introduce parentheses? + // The result depends on the kind of expression. + // Each expression type that might need parentheses + // calls addParen with its own precedence. + // If parentheses are necessary, addParen prints the + // opening parenthesis and sets parenthesized so that + // the code after the switch can print the closing one. + parenthesized := false + addParen := func(prec int) { + if prec < outerPrec { + p.printf("(") + p.depth++ + parenthesized = true + } + } + + switch v := v.(type) { + default: + panic(fmt.Errorf("printer: unexpected type %T", v)) + + case *LiteralExpr: + p.printf("%s", v.Token) + + case *StringExpr: + // If the Token is a correct quoting of Value, use it. + // This preserves the specific escaping choices that + // BUILD authors have made, and it also works around + // b/7272572. + if strings.HasPrefix(v.Token, `"`) { + s, triple, err := unquote(v.Token) + if s == v.Value && triple == v.TripleQuote && err == nil { + p.printf("%s", v.Token) + break + } + } + + p.printf("%s", quote(v.Value, v.TripleQuote)) + + case *DotExpr: + addParen(precSuffix) + p.expr(v.X, precSuffix) + p.printf(".%s", v.Name) + + case *IndexExpr: + addParen(precSuffix) + p.expr(v.X, precSuffix) + p.printf("[") + p.expr(v.Y, precLow) + p.printf("]") + + case *KeyValueExpr: + p.expr(v.Key, precLow) + p.printf(": ") + p.expr(v.Value, precLow) + + case *SliceExpr: + addParen(precSuffix) + p.expr(v.X, precSuffix) + p.printf("[") + if v.Y != nil { + p.expr(v.Y, precLow) + } + p.printf(":") + if v.Z != nil { + p.expr(v.Z, precLow) + } + p.printf("]") + + case *UnaryExpr: + addParen(precUnary) + if v.Op == "not" { + p.printf("not ") // Requires a space after it. + } else { + p.printf("%s", v.Op) + } + p.expr(v.X, precUnary) + + case *LambdaExpr: + addParen(precColon) + p.printf("lambda ") + for i, name := range v.Var { + if i > 0 { + p.printf(", ") + } + p.expr(name, precLow) + } + p.printf(": ") + p.expr(v.Expr, precColon) + + case *BinaryExpr: + // Precedence: use the precedence of the operator. + // Since all binary expressions format left-to-right, + // it is okay for the left side to reuse the same operator + // without parentheses, so we use prec for v.X. + // For the same reason, the right side cannot reuse the same + // operator, or else a parse tree for a + (b + c), where the ( ) are + // not present in the source, will format as a + b + c, which + // means (a + b) + c. Treat the right expression as appearing + // in a context one precedence level higher: use prec+1 for v.Y. + // + // Line breaks: if we are to break the line immediately after + // the operator, introduce a margin at the current column, + // so that the second operand lines up with the first one and + // also so that neither operand can use space to the left. + // If the operator is an =, indent the right side another 4 spaces. + prec := opPrec[v.Op] + addParen(prec) + m := p.margin + if v.LineBreak { + p.margin = p.indent() + if v.Op == "=" { + p.margin += 4 + } + } + + p.expr(v.X, prec) + p.printf(" %s", v.Op) + if v.LineBreak { + p.breakline() + } else { + p.printf(" ") + } + p.expr(v.Y, prec+1) + p.margin = m + + case *ParenExpr: + p.seq("()", []Expr{v.X}, &v.End, modeParen, false, v.ForceMultiLine) + + case *CallExpr: + addParen(precSuffix) + p.expr(v.X, precSuffix) + p.seq("()", v.List, &v.End, modeCall, v.ForceCompact, v.ForceMultiLine) + + case *ListExpr: + p.seq("[]", v.List, &v.End, modeList, false, v.ForceMultiLine) + + case *TupleExpr: + p.seq("()", v.List, &v.End, modeTuple, v.ForceCompact, v.ForceMultiLine) + + case *DictExpr: + var list []Expr + for _, x := range v.List { + list = append(list, x) + } + p.seq("{}", list, &v.End, modeDict, false, v.ForceMultiLine) + + case *ListForExpr: + p.listFor(v) + + case *ConditionalExpr: + addParen(precSuffix) + p.expr(v.Then, precSuffix) + p.printf(" if ") + p.expr(v.Test, precSuffix) + p.printf(" else ") + p.expr(v.Else, precSuffix) + } + + // Add closing parenthesis if needed. + if parenthesized { + p.depth-- + p.printf(")") + } + + // Queue end-of-line comments for printing when we + // reach the end of the line. + p.comment = append(p.comment, v.Comment().Suffix...) +} + +// A seqMode describes a formatting mode for a sequence of values, +// like a list or call arguments. +type seqMode int + +const ( + _ seqMode = iota + + modeCall // f(x) + modeList // [x] + modeTuple // (x,) + modeParen // (x) + modeDict // {x:y} +) + +// seq formats a list of values inside a given bracket pair (brack = "()", "[]", "{}"). +// The end node holds any trailing comments to be printed just before the +// closing bracket. +// The mode parameter specifies the sequence mode (see above). +// If multiLine is true, seq avoids the compact form even +// for 0- and 1-element sequences. +func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCompact, forceMultiLine bool) { + p.printf("%s", brack[:1]) + p.depth++ + + // If there are line comments, force multiline + // so we can print the comments before the closing bracket. + for _, x := range list { + if len(x.Comment().Before) > 0 { + forceMultiLine = true + } + } + if len(end.Before) > 0 { + forceMultiLine = true + } + + // Resolve possibly ambiguous call arguments explicitly + // instead of depending on implicit resolution in logic below. + if forceMultiLine { + forceCompact = false + } + + switch { + case len(list) == 0 && !forceMultiLine: + // Compact form: print nothing. + + case len(list) == 1 && !forceMultiLine: + // Compact form. + p.expr(list[0], precLow) + // Tuple must end with comma, to mark it as a tuple. + if mode == modeTuple { + p.printf(",") + } + + case forceCompact: + // Compact form but multiple elements. + for i, x := range list { + if i > 0 { + p.printf(", ") + } + p.expr(x, precLow) + } + + default: + // Multi-line form. + p.margin += 4 + for i, x := range list { + // If we are about to break the line before the first + // element and there are trailing end-of-line comments + // waiting to be printed, delay them and print them as + // whole-line comments preceding that element. + // Do this by printing a newline ourselves and positioning + // so that the end-of-line comment, with the two spaces added, + // will line up with the current margin. + if i == 0 && len(p.comment) > 0 { + p.printf("\n%*s", p.margin-2, "") + } + + p.newline() + p.expr(x, precLow) + if mode != modeParen || i+1 < len(list) { + p.printf(",") + } + } + // Final comments. + for _, com := range end.Before { + p.newline() + p.printf("%s", strings.TrimSpace(com.Token)) + } + p.margin -= 4 + p.newline() + } + p.depth-- + p.printf("%s", brack[1:]) +} + +// listFor formats a ListForExpr (list comprehension). +// The single-line form is: +// [x for y in z if c] +// +// and the multi-line form is: +// [ +// x +// for y in z +// if c +// ] +// +func (p *printer) listFor(v *ListForExpr) { + multiLine := v.ForceMultiLine || len(v.End.Before) > 0 + + // space breaks the line in multiline mode + // or else prints a space. + space := func() { + if multiLine { + p.breakline() + } else { + p.printf(" ") + } + } + + if v.Brack != "" { + p.depth++ + p.printf("%s", v.Brack[:1]) + } + + if multiLine { + if v.Brack != "" { + p.margin += 4 + } + p.newline() + } + + p.expr(v.X, precLow) + + for _, c := range v.For { + space() + p.printf("for ") + for i, name := range c.Var { + if i > 0 { + p.printf(", ") + } + p.expr(name, precLow) + } + p.printf(" in ") + p.expr(c.Expr, precLow) + } + + for _, c := range v.If { + space() + p.printf("if ") + p.expr(c.Cond, precLow) + } + + if multiLine { + for _, com := range v.End.Before { + p.newline() + p.printf("%s", strings.TrimSpace(com.Token)) + } + if v.Brack != "" { + p.margin -= 4 + } + p.newline() + } + + if v.Brack != "" { + p.printf("%s", v.Brack[1:]) + p.depth-- + } +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/quote.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/quote.go new file mode 100644 index 0000000000000000000000000000000000000000..d5ffe8d452553f6a11e90525090ad6a6d13c7edc --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/quote.go @@ -0,0 +1,262 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Python quoted strings. + +package build + +import ( + "bytes" + "fmt" + "strconv" + "strings" +) + +// unesc maps single-letter chars following \ to their actual values. +var unesc = [256]byte{ + 'a': '\a', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t', + 'v': '\v', + '\\': '\\', + '\'': '\'', + '"': '"', +} + +// esc maps escape-worthy bytes to the char that should follow \. +var esc = [256]byte{ + '\a': 'a', + '\b': 'b', + '\f': 'f', + '\n': 'n', + '\r': 'r', + '\t': 't', + '\v': 'v', + '\\': '\\', + '\'': '\'', + '"': '"', +} + +// notEsc is a list of characters that can follow a \ in a string value +// without having to escape the \. That is, since ( is in this list, we +// quote the Go string "foo\\(bar" as the Python literal "foo\(bar". +// This really does happen in BUILD files, especially in strings +// being used as shell arguments containing regular expressions. +const notEsc = " !#$%&()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~" + +// unquote unquotes the quoted string, returning the actual +// string value, whether the original was triple-quoted, and +// an error describing invalid input. +func unquote(quoted string) (s string, triple bool, err error) { + // Check for raw prefix: means don't interpret the inner \. + raw := false + if strings.HasPrefix(quoted, "r") { + raw = true + quoted = quoted[1:] + } + + if len(quoted) < 2 { + err = fmt.Errorf("string literal too short") + return + } + + if quoted[0] != '"' && quoted[0] != '\'' || quoted[0] != quoted[len(quoted)-1] { + err = fmt.Errorf("string literal has invalid quotes") + } + + // Check for triple quoted string. + quote := quoted[0] + if len(quoted) >= 6 && quoted[1] == quote && quoted[2] == quote && quoted[:3] == quoted[len(quoted)-3:] { + triple = true + quoted = quoted[3 : len(quoted)-3] + } else { + quoted = quoted[1 : len(quoted)-1] + } + + // Now quoted is the quoted data, but no quotes. + // If we're in raw mode or there are no escapes, we're done. + if raw || !strings.Contains(quoted, `\`) { + s = quoted + return + } + + // Otherwise process quoted string. + // Each iteration processes one escape sequence along with the + // plain text leading up to it. + var buf bytes.Buffer + for { + // Remove prefix before escape sequence. + i := strings.Index(quoted, `\`) + if i < 0 { + i = len(quoted) + } + buf.WriteString(quoted[:i]) + quoted = quoted[i:] + + if len(quoted) == 0 { + break + } + + // Process escape sequence. + if len(quoted) == 1 { + err = fmt.Errorf(`truncated escape sequence \`) + return + } + + switch quoted[1] { + default: + // In Python, if \z (for some byte z) is not a known escape sequence + // then it appears as literal text in the string. + buf.WriteString(quoted[:2]) + quoted = quoted[2:] + + case '\n': + // Ignore the escape and the line break. + quoted = quoted[2:] + + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"': + // One-char escape + buf.WriteByte(unesc[quoted[1]]) + quoted = quoted[2:] + + case '0', '1', '2', '3', '4', '5', '6', '7': + // Octal escape, up to 3 digits. + n := int(quoted[1] - '0') + quoted = quoted[2:] + for i := 1; i < 3; i++ { + if len(quoted) == 0 || quoted[0] < '0' || '7' < quoted[0] { + break + } + n = n*8 + int(quoted[0]-'0') + quoted = quoted[1:] + } + if n >= 256 { + // NOTE: Python silently discards the high bit, + // so that '\541' == '\141' == 'a'. + // Let's see if we can avoid doing that in BUILD files. + err = fmt.Errorf(`invalid escape sequence \%03o`, n) + return + } + buf.WriteByte(byte(n)) + + case 'x': + // Hexadecimal escape, exactly 2 digits. + if len(quoted) < 4 { + err = fmt.Errorf(`truncated escape sequence %s`, quoted) + return + } + n, err1 := strconv.ParseInt(quoted[2:4], 16, 0) + if err1 != nil { + err = fmt.Errorf(`invalid escape sequence %s`, quoted[:4]) + return + } + buf.WriteByte(byte(n)) + quoted = quoted[4:] + } + } + + s = buf.String() + return +} + +// indexByte returns the index of the first instance of b in s, or else -1. +func indexByte(s string, b byte) int { + for i := 0; i < len(s); i++ { + if s[i] == b { + return i + } + } + return -1 +} + +// hex is a list of the hexadecimal digits, for use in quoting. +// We always print lower-case hexadecimal. +const hex = "0123456789abcdef" + +// quote returns the quoted form of the string value "x". +// If triple is true, quote uses the triple-quoted form """x""". +func quote(unquoted string, triple bool) string { + q := `"` + if triple { + q = `"""` + } + + var buf bytes.Buffer + buf.WriteString(q) + + for i := 0; i < len(unquoted); i++ { + c := unquoted[i] + if c == '"' && triple && (i+1 < len(unquoted) && unquoted[i+1] != '"' || i+2 < len(unquoted) && unquoted[i+2] != '"') { + // Can pass up to two quotes through, because they are followed by a non-quote byte. + buf.WriteByte(c) + if i+1 < len(unquoted) && unquoted[i+1] == '"' { + buf.WriteByte(c) + i++ + } + continue + } + if triple && c == '\n' { + // Can allow newline in triple-quoted string. + buf.WriteByte(c) + continue + } + if c == '\'' { + // Can allow ' since we always use ". + buf.WriteByte(c) + continue + } + if c == '\\' { + if i+1 < len(unquoted) && indexByte(notEsc, unquoted[i+1]) >= 0 { + // Can pass \ through when followed by a byte that + // known not to be a valid escape sequence and also + // that does not trigger an escape sequence of its own. + // Use this, because various BUILD files do. + buf.WriteByte('\\') + buf.WriteByte(unquoted[i+1]) + i++ + continue + } + } + if esc[c] != 0 { + buf.WriteByte('\\') + buf.WriteByte(esc[c]) + continue + } + if c < 0x20 || c >= 0x80 { + // BUILD files are supposed to be Latin-1, so escape all control and high bytes. + // I'd prefer to use \x here, but Blaze does not implement + // \x in quoted strings (b/7272572). + buf.WriteByte('\\') + buf.WriteByte(hex[c>>6]) // actually octal but reusing hex digits 0-7. + buf.WriteByte(hex[(c>>3)&7]) + buf.WriteByte(hex[c&7]) + /* + buf.WriteByte('\\') + buf.WriteByte('x') + buf.WriteByte(hex[c>>4]) + buf.WriteByte(hex[c&0xF]) + */ + continue + } + buf.WriteByte(c) + continue + } + + buf.WriteString(q) + return buf.String() +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rewrite.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rewrite.go new file mode 100644 index 0000000000000000000000000000000000000000..5be810dc2a97e033e87f73d548105c07faf7bd98 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rewrite.go @@ -0,0 +1,803 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Rewriting of high-level (not purely syntactic) BUILD constructs. + +package build + +import ( + "regexp" + "sort" + "strings" +) + +// For debugging: flag to disable certain rewrites. +var DisableRewrites []string + +// disabled reports whether the named rewrite is disabled. +func disabled(name string) bool { + for _, x := range DisableRewrites { + if name == x { + return true + } + } + return false +} + +// For debugging: allow sorting of these lists even with sorting otherwise disabled. +var AllowSort []string + +// allowedSort reports whether sorting is allowed in the named context. +func allowedSort(name string) bool { + for _, x := range AllowSort { + if name == x { + return true + } + } + return false +} + +// Rewrite applies the high-level Buildifier rewrites to f, modifying it in place. +// If info is non-nil, Rewrite updates it with information about the rewrite. +func Rewrite(f *File, info *RewriteInfo) { + // Allocate an info so that helpers can assume it's there. + if info == nil { + info = new(RewriteInfo) + } + + for _, r := range rewrites { + if !disabled(r.name) { + r.fn(f, info) + } + } +} + +// RewriteInfo collects information about what Rewrite did. +type RewriteInfo struct { + EditLabel int // number of label strings edited + NameCall int // number of calls with argument names added + SortCall int // number of call argument lists sorted + SortStringList int // number of string lists sorted + UnsafeSort int // number of unsafe string lists sorted + Log []string // log entries - may change +} + +func (info *RewriteInfo) String() string { + s := "" + if info.EditLabel > 0 { + s += " label" + } + if info.NameCall > 0 { + s += " callname" + } + if info.SortCall > 0 { + s += " callsort" + } + if info.SortStringList > 0 { + s += " listsort" + } + if info.UnsafeSort > 0 { + s += " unsafesort" + } + if s != "" { + s = s[1:] + } + return s +} + +// rewrites is the list of all Buildifier rewrites, in the order in which they are applied. +// The order here matters: for example, label canonicalization must happen +// before sorting lists of strings. +var rewrites = []struct { + name string + fn func(*File, *RewriteInfo) +}{ + {"callsort", sortCallArgs}, + {"label", fixLabels}, + {"listsort", sortStringLists}, + {"multiplus", fixMultilinePlus}, +} + +// leaveAlone reports whether any of the nodes on the stack are marked +// with a comment containing "buildifier: leave-alone". +func leaveAlone(stk []Expr, final Expr) bool { + for _, x := range stk { + if leaveAlone1(x) { + return true + } + } + if final != nil && leaveAlone1(final) { + return true + } + return false +} + +// hasComment reports whether x is marked with a comment that +// after being converted to lower case, contains the specified text. +func hasComment(x Expr, text string) bool { + for _, com := range x.Comment().Before { + if strings.Contains(strings.ToLower(com.Token), text) { + return true + } + } + return false +} + +// leaveAlone1 reports whether x is marked with a comment containing +// "buildifier: leave-alone", case-insensitive. +func leaveAlone1(x Expr) bool { + return hasComment(x, "buildifier: leave-alone") +} + +// doNotSort reports whether x is marked with a comment containing +// "do not sort", case-insensitive. +func doNotSort(x Expr) bool { + return hasComment(x, "do not sort") +} + +// keepSorted reports whether x is marked with a comment containing +// "keep sorted", case-insensitive. +func keepSorted(x Expr) bool { + return hasComment(x, "keep sorted") +} + +// fixLabels rewrites labels into a canonical form. +// +// First, it joins labels written as string addition, turning +// "//x" + ":y" (usually split across multiple lines) into "//x:y". +// +// Second, it removes redundant target qualifiers, turning +// "//third_party/m4:m4" into "//third_party/m4". +// +func fixLabels(f *File, info *RewriteInfo) { + joinLabel := func(p *Expr) { + add, ok := (*p).(*BinaryExpr) + if !ok || add.Op != "+" { + return + } + str1, ok := add.X.(*StringExpr) + if !ok || !strings.HasPrefix(str1.Value, "//") || strings.Contains(str1.Value, " ") { + return + } + str2, ok := add.Y.(*StringExpr) + if !ok || strings.Contains(str2.Value, " ") { + return + } + info.EditLabel++ + str1.Value += str2.Value + + // Deleting nodes add and str2. + // Merge comments from add, str1, and str2 and save in str1. + com1 := add.Comment() + com2 := str1.Comment() + com3 := str2.Comment() + com1.Before = append(com1.Before, com2.Before...) + com1.Before = append(com1.Before, com3.Before...) + com1.Suffix = append(com1.Suffix, com2.Suffix...) + com1.Suffix = append(com1.Suffix, com3.Suffix...) + *str1.Comment() = *com1 + + *p = str1 + } + + // labelRE matches label strings //x/y/z:abc. + // $1 is //x/y/z, $2 is x/y/, $3 is z, $4 is :abc, and $5 is abc. + labelRE := regexp.MustCompile(`^(//(.*/)?([^:]+))(:([^:]+))?$`) + + shortenLabel := func(v Expr) { + str, ok := v.(*StringExpr) + if !ok { + return + } + m := labelRE.FindStringSubmatch(str.Value) + if m == nil { + return + } + if m[3] == m[5] { + info.EditLabel++ + str.Value = m[1] + } + } + + Walk(f, func(v Expr, stk []Expr) { + switch v := v.(type) { + case *CallExpr: + if leaveAlone(stk, v) { + return + } + for i := range v.List { + if leaveAlone1(v.List[i]) { + continue + } + as, ok := v.List[i].(*BinaryExpr) + if !ok || as.Op != "=" { + continue + } + key, ok := as.X.(*LiteralExpr) + if !ok || !isLabelArg[key.Token] || labelBlacklist[callName(v)+"."+key.Token] { + continue + } + if leaveAlone1(as.Y) { + continue + } + if list, ok := as.Y.(*ListExpr); ok { + for i := range list.List { + if leaveAlone1(list.List[i]) { + continue + } + joinLabel(&list.List[i]) + shortenLabel(list.List[i]) + } + } else { + joinLabel(&as.Y) + shortenLabel(as.Y) + } + } + } + }) +} + +// callName returns the name of the rule being called by call. +// If the call is not to a literal rule name, callName returns "". +func callName(call *CallExpr) string { + rule, ok := call.X.(*LiteralExpr) + if !ok { + return "" + } + return rule.Token +} + +// sortCallArgs sorts lists of named arguments to a call. +func sortCallArgs(f *File, info *RewriteInfo) { + Walk(f, func(v Expr, stk []Expr) { + call, ok := v.(*CallExpr) + if !ok { + return + } + if leaveAlone(stk, call) { + return + } + rule := callName(call) + if rule == "" { + return + } + + // Find the tail of the argument list with named arguments. + start := len(call.List) + for start > 0 && argName(call.List[start-1]) != "" { + start-- + } + + // Record information about each arg into a sortable list. + var args namedArgs + for i, x := range call.List[start:] { + name := argName(x) + args = append(args, namedArg{ruleNamePriority(rule, name), name, i, x}) + } + + // Sort the list and put the args back in the new order. + if sort.IsSorted(args) { + return + } + info.SortCall++ + sort.Sort(args) + for i, x := range args { + call.List[start+i] = x.expr + } + }) +} + +// ruleNamePriority maps a rule argument name to its sorting priority. +// It could use the auto-generated per-rule tables but for now it just +// falls back to the original list. +func ruleNamePriority(rule, arg string) int { + return namePriority[arg] + /* + list := ruleArgOrder[rule] + if len(list) == 0 { + return namePriority[arg] + } + for i, x := range list { + if x == arg { + return i + } + } + return len(list) + */ +} + +// namePriority maps an argument name to its sorting priority. +// +// NOTE(bazel-team): These are the old buildifier rules. It is likely that this table +// will change, perhaps swapping in a separate table for each call, +// derived from the order used in the Build Encyclopedia. +var namePriority = map[string]int{ + "name": -99, + "gwt_name": -98, + "package_name": -97, + "visible_node_name": -96, // for boq_initial_css_modules and boq_jswire_test_suite + "size": -95, + "timeout": -94, + "testonly": -93, + "src": -92, + "srcdir": -91, + "srcs": -90, + "out": -89, + "outs": -88, + "hdrs": -87, + "has_services": -86, // before api versions, for proto + "include": -85, // before exclude, for glob + "of": -84, // for check_dependencies + "baseline": -83, // for searchbox_library + // All others sort here, at 0. + "destdir": 1, + "exports": 2, + "runtime_deps": 3, + "deps": 4, + "implementation": 5, + "implements": 6, + "alwayslink": 7, +} + +// If x is of the form key=value, argName returns the string key. +// Otherwise argName returns "". +func argName(x Expr) string { + if as, ok := x.(*BinaryExpr); ok && as.Op == "=" { + if id, ok := as.X.(*LiteralExpr); ok { + return id.Token + } + } + return "" +} + +// A namedArg records information needed for sorting +// a named call argument into its proper position. +type namedArg struct { + priority int // kind of name; first sort key + name string // name; second sort key + index int // original index; final sort key + expr Expr // name=value argument +} + +// namedArgs is a slice of namedArg that implements sort.Interface +type namedArgs []namedArg + +func (x namedArgs) Len() int { return len(x) } +func (x namedArgs) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x namedArgs) Less(i, j int) bool { + p := x[i] + q := x[j] + if p.priority != q.priority { + return p.priority < q.priority + } + if p.name != q.name { + return p.name < q.name + } + return p.index < q.index +} + +// sortStringLists sorts lists of string literals used as specific rule arguments. +func sortStringLists(f *File, info *RewriteInfo) { + Walk(f, func(v Expr, stk []Expr) { + switch v := v.(type) { + case *CallExpr: + if leaveAlone(stk, v) { + return + } + rule := callName(v) + for _, arg := range v.List { + if leaveAlone1(arg) { + continue + } + as, ok := arg.(*BinaryExpr) + if !ok || as.Op != "=" || leaveAlone1(as) || doNotSort(as) { + continue + } + key, ok := as.X.(*LiteralExpr) + if !ok { + continue + } + context := rule + "." + key.Token + if !isSortableListArg[key.Token] || sortableBlacklist[context] { + continue + } + if disabled("unsafesort") && !sortableWhitelist[context] && !allowedSort(context) { + continue + } + sortStringList(as.Y, info, context) + } + case *BinaryExpr: + if disabled("unsafesort") { + return + } + // "keep sorted" comment on x = list forces sorting of list. + as := v + if as.Op == "=" && keepSorted(as) { + sortStringList(as.Y, info, "?") + } + case *KeyValueExpr: + if disabled("unsafesort") { + return + } + // "keep sorted" before key: list also forces sorting of list. + if keepSorted(v) { + sortStringList(v.Value, info, "?") + } + case *ListExpr: + if disabled("unsafesort") { + return + } + // "keep sorted" comment above first list element also forces sorting of list. + if len(v.List) > 0 && keepSorted(v.List[0]) { + sortStringList(v, info, "?") + } + } + }) +} + +// SortStringList sorts x, a list of strings. +func SortStringList(x Expr) { + sortStringList(x, nil, "") +} + +// sortStringList sorts x, a list of strings. +// The list is broken by non-strings and by blank lines and comments into chunks. +// Each chunk is sorted in place. +func sortStringList(x Expr, info *RewriteInfo, context string) { + list, ok := x.(*ListExpr) + if !ok || len(list.List) < 2 || doNotSort(list.List[0]) { + return + } + + forceSort := keepSorted(list.List[0]) + + // TODO(bazel-team): Decide how to recognize lists that cannot + // be sorted. Avoiding all lists with comments avoids sorting + // lists that say explicitly, in some form or another, why they + // cannot be sorted. For example, many cc_test rules require + // certain order in their deps attributes. + if !forceSort { + if line, _ := hasComments(list); line { + return + } + } + + // Sort chunks of the list with no intervening blank lines or comments. + for i := 0; i < len(list.List); { + if _, ok := list.List[i].(*StringExpr); !ok { + i++ + continue + } + + j := i + 1 + for ; j < len(list.List); j++ { + if str, ok := list.List[j].(*StringExpr); !ok || len(str.Before) > 0 { + break + } + } + + var chunk []stringSortKey + for index, x := range list.List[i:j] { + chunk = append(chunk, makeSortKey(index, x.(*StringExpr))) + } + if !sort.IsSorted(byStringExpr(chunk)) || !isUniq(chunk) { + if info != nil { + info.SortStringList++ + if !sortableWhitelist[context] { + info.UnsafeSort++ + info.Log = append(info.Log, "sort:"+context) + } + } + before := chunk[0].x.Comment().Before + chunk[0].x.Comment().Before = nil + + sort.Sort(byStringExpr(chunk)) + chunk = uniq(chunk) + + chunk[0].x.Comment().Before = before + for offset, key := range chunk { + list.List[i+offset] = key.x + } + list.List = append(list.List[:(i+len(chunk))], list.List[j:]...) + } + + i = j + } +} + +// uniq removes duplicates from a list, which must already be sorted. +// It edits the list in place. +func uniq(sortedList []stringSortKey) []stringSortKey { + out := sortedList[:0] + for _, sk := range sortedList { + if len(out) == 0 || sk.value != out[len(out)-1].value { + out = append(out, sk) + } + } + return out +} + +// isUniq reports whether the sorted list only contains unique elements. +func isUniq(list []stringSortKey) bool { + for i := range list { + if i+1 < len(list) && list[i].value == list[i+1].value { + return false + } + } + return true +} + +// If stk describes a call argument like rule(arg=...), callArgName +// returns the name of that argument, formatted as "rule.arg". +func callArgName(stk []Expr) string { + n := len(stk) + if n < 2 { + return "" + } + arg := argName(stk[n-1]) + if arg == "" { + return "" + } + call, ok := stk[n-2].(*CallExpr) + if !ok { + return "" + } + rule, ok := call.X.(*LiteralExpr) + if !ok { + return "" + } + return rule.Token + "." + arg +} + +// A stringSortKey records information about a single string literal to be +// sorted. The strings are first grouped into four phases: most strings, +// strings beginning with ":", strings beginning with "//", and strings +// beginning with "@". The next significant part of the comparison is the list +// of elements in the value, where elements are split at `.' and `:'. Finally +// we compare by value and break ties by original index. +type stringSortKey struct { + phase int + split []string + value string + original int + x Expr +} + +func makeSortKey(index int, x *StringExpr) stringSortKey { + key := stringSortKey{ + value: x.Value, + original: index, + x: x, + } + + switch { + case strings.HasPrefix(x.Value, ":"): + key.phase = 1 + case strings.HasPrefix(x.Value, "//"): + key.phase = 2 + case strings.HasPrefix(x.Value, "@"): + key.phase = 3 + } + + key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".") + return key +} + +// byStringExpr implements sort.Interface for a list of stringSortKey. +type byStringExpr []stringSortKey + +func (x byStringExpr) Len() int { return len(x) } +func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byStringExpr) Less(i, j int) bool { + xi := x[i] + xj := x[j] + + if xi.phase != xj.phase { + return xi.phase < xj.phase + } + for k := 0; k < len(xi.split) && k < len(xj.split); k++ { + if xi.split[k] != xj.split[k] { + return xi.split[k] < xj.split[k] + } + } + if len(xi.split) != len(xj.split) { + return len(xi.split) < len(xj.split) + } + if xi.value != xj.value { + return xi.value < xj.value + } + return xi.original < xj.original +} + +// fixMultilinePlus turns +// +// ... + +// [ ... ] +// +// ... + +// call(...) +// +// into +// ... + [ +// ... +// ] +// +// ... + call( +// ... +// ) +// +// which typically works better with our aggressively compact formatting. +func fixMultilinePlus(f *File, info *RewriteInfo) { + + // List manipulation helpers. + // As a special case, we treat f([...]) as a list, mainly + // for glob. + + // isList reports whether x is a list. + var isList func(x Expr) bool + isList = func(x Expr) bool { + switch x := x.(type) { + case *ListExpr: + return true + case *CallExpr: + if len(x.List) == 1 { + return isList(x.List[0]) + } + } + return false + } + + // isMultiLine reports whether x is a multiline list. + var isMultiLine func(Expr) bool + isMultiLine = func(x Expr) bool { + switch x := x.(type) { + case *ListExpr: + return x.ForceMultiLine || len(x.List) > 1 + case *CallExpr: + if x.ForceMultiLine || len(x.List) > 1 && !x.ForceCompact { + return true + } + if len(x.List) == 1 { + return isMultiLine(x.List[0]) + } + } + return false + } + + // forceMultiLine tries to force the list x to use a multiline form. + // It reports whether it was successful. + var forceMultiLine func(Expr) bool + forceMultiLine = func(x Expr) bool { + switch x := x.(type) { + case *ListExpr: + // Already multi line? + if x.ForceMultiLine { + return true + } + // If this is a list containing a list, force the + // inner list to be multiline instead. + if len(x.List) == 1 && forceMultiLine(x.List[0]) { + return true + } + x.ForceMultiLine = true + return true + + case *CallExpr: + if len(x.List) == 1 { + return forceMultiLine(x.List[0]) + } + } + return false + } + + skip := map[Expr]bool{} + Walk(f, func(v Expr, stk []Expr) { + if skip[v] { + return + } + bin, ok := v.(*BinaryExpr) + if !ok || bin.Op != "+" { + return + } + + // Found a +. + // w + x + y + z parses as ((w + x) + y) + z, + // so chase down the left side to make a list of + // all the things being added together, separated + // by the BinaryExprs that join them. + // Mark them as "skip" so that when Walk recurses + // into the subexpressions, we won't reprocess them. + var all []Expr + for { + all = append(all, bin.Y, bin) + bin1, ok := bin.X.(*BinaryExpr) + if !ok || bin1.Op != "+" { + break + } + bin = bin1 + skip[bin] = true + } + all = append(all, bin.X) + + // Because the outermost expression was the + // rightmost one, the list is backward. Reverse it. + for i, j := 0, len(all)-1; i < j; i, j = i+1, j-1 { + all[i], all[j] = all[j], all[i] + } + + // The 'all' slice is alternating addends and BinaryExpr +'s: + // w, +, x, +, y, +, z + // If there are no lists involved, don't rewrite anything. + haveList := false + for i := 0; i < len(all); i += 2 { + if isList(all[i]) { + haveList = true + break + } + } + if !haveList { + return + } + + // Okay, there are lists. + // Consider each + next to a line break. + for i := 1; i < len(all); i += 2 { + bin := all[i].(*BinaryExpr) + if !bin.LineBreak { + continue + } + + // We're going to break the line after the +. + // If it is followed by a list, force that to be + // multiline instead. + if forceMultiLine(all[i+1]) { + bin.LineBreak = false + continue + } + + // If the previous list was multiline already, + // don't bother with the line break after + // the +. + if isMultiLine(all[i-1]) { + bin.LineBreak = false + continue + } + } + }) +} + +// hasComments reports whether any comments are associated with +// the list or its elements. +func hasComments(list *ListExpr) (line, suffix bool) { + com := list.Comment() + if len(com.Before) > 0 || len(com.After) > 0 || len(list.End.Before) > 0 { + line = true + } + if len(com.Suffix) > 0 { + suffix = true + } + for _, elem := range list.List { + com := elem.Comment() + if len(com.Before) > 0 { + line = true + } + if len(com.Suffix) > 0 { + suffix = true + } + } + return +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rule.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rule.go new file mode 100644 index 0000000000000000000000000000000000000000..8793a40e43b505bc4ad45a1f4517e168602d20ef --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/rule.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Rule-level API for inspecting and modifying a build.File syntax tree. + +package build + +// A Rule represents a single BUILD rule. +type Rule struct { + Call *CallExpr +} + +// Rules returns the rules in the file of the given kind (such as "go_library"). +// If kind == "", Rules returns all rules in the file. +func (f *File) Rules(kind string) []*Rule { + var all []*Rule + for _, stmt := range f.Stmt { + call, ok := stmt.(*CallExpr) + if !ok { + continue + } + k, ok := call.X.(*LiteralExpr) + if !ok { + continue + } + if kind != "" && k.Token != kind { + continue + } + all = append(all, &Rule{call}) + } + return all +} + +// RuleAt returns the rule in the file that starts at the specified line, or null if no such rule. +func (f *File) RuleAt(linenum int) *Rule { + for _, stmt := range f.Stmt { + call, ok := stmt.(*CallExpr) + if !ok { + continue + } + start, end := call.X.Span() + if start.Line <= linenum && linenum <= end.Line { + return &Rule{call} + } + } + return nil +} + +// DelRules removes rules with the given kind and name from the file. +// An empty kind matches all kinds; an empty name matches all names. +// It returns the number of rules that were deleted. +func (f *File) DelRules(kind, name string) int { + var i int + for _, stmt := range f.Stmt { + if call, ok := stmt.(*CallExpr); ok { + if k, ok := call.X.(*LiteralExpr); ok { + if kind == "" || k.Token == kind { + r := &Rule{call} + if name == "" || r.AttrString("name") == name { + continue + } + } + } + } + f.Stmt[i] = stmt + i++ + } + n := len(f.Stmt) - i + f.Stmt = f.Stmt[:i] + return n +} + +// Kind returns the rule's kind (such as "go_library"). +func (r *Rule) Kind() string { + return r.Call.X.(*LiteralExpr).Token +} + +// SetKind changes rule's kind (such as "go_library"). +func (r *Rule) SetKind(kind string) { + r.Call.X.(*LiteralExpr).Token = kind +} + +// Name returns the rule's target name. +// If the rule has no target name, Name returns the empty string. +func (r *Rule) Name() string { + return r.AttrString("name") +} + +// AttrKeys returns the keys of all the rule's attributes. +func (r *Rule) AttrKeys() []string { + var keys []string + for _, expr := range r.Call.List { + if binExpr, ok := expr.(*BinaryExpr); ok && binExpr.Op == "=" { + if keyExpr, ok := binExpr.X.(*LiteralExpr); ok { + keys = append(keys, keyExpr.Token) + } + } + } + return keys +} + +// AttrDefn returns the BinaryExpr defining the rule's attribute with the given key. +// That is, the result is a *BinaryExpr with Op == "=". +// If the rule has no such attribute, AttrDefn returns nil. +func (r *Rule) AttrDefn(key string) *BinaryExpr { + for _, kv := range r.Call.List { + as, ok := kv.(*BinaryExpr) + if !ok || as.Op != "=" { + continue + } + k, ok := as.X.(*LiteralExpr) + if !ok || k.Token != key { + continue + } + return as + } + return nil +} + +// Attr returns the value of the rule's attribute with the given key +// (such as "name" or "deps"). +// If the rule has no such attribute, Attr returns nil. +func (r *Rule) Attr(key string) Expr { + as := r.AttrDefn(key) + if as == nil { + return nil + } + return as.Y +} + +// DelAttr deletes the rule's attribute with the named key. +// It returns the old value of the attribute, or nil if the attribute was not found. +func (r *Rule) DelAttr(key string) Expr { + list := r.Call.List + for i, kv := range list { + as, ok := kv.(*BinaryExpr) + if !ok || as.Op != "=" { + continue + } + k, ok := as.X.(*LiteralExpr) + if !ok || k.Token != key { + continue + } + copy(list[i:], list[i+1:]) + r.Call.List = list[:len(list)-1] + return as.Y + } + return nil +} + +// SetAttr sets the rule's attribute with the given key to value. +// If the rule has no attribute with the key, SetAttr appends +// one to the end of the rule's attribute list. +func (r *Rule) SetAttr(key string, val Expr) { + as := r.AttrDefn(key) + if as != nil { + as.Y = val + return + } + + r.Call.List = append(r.Call.List, + &BinaryExpr{ + X: &LiteralExpr{Token: key}, + Op: "=", + Y: val, + }, + ) +} + +// AttrLiteral returns the literal form of the rule's attribute +// with the given key (such as "cc_api_version"), only when +// that value is an identifier or number. +// If the rule has no such attribute or the attribute is not an identifier or number, +// AttrLiteral returns "". +func (r *Rule) AttrLiteral(key string) string { + lit, ok := r.Attr(key).(*LiteralExpr) + if !ok { + return "" + } + return lit.Token +} + +// AttrString returns the value of the rule's attribute +// with the given key (such as "name"), as a string. +// If the rule has no such attribute or the attribute has a non-string value, +// Attr returns the empty string. +func (r *Rule) AttrString(key string) string { + str, ok := r.Attr(key).(*StringExpr) + if !ok { + return "" + } + return str.Value +} + +// AttrStrings returns the value of the rule's attribute +// with the given key (such as "srcs"), as a []string. +// If the rule has no such attribute or the attribute is not +// a list of strings, AttrStrings returns a nil slice. +func (r *Rule) AttrStrings(key string) []string { + return Strings(r.Attr(key)) +} + +// Strings returns expr as a []string. +// If expr is not a list of string literals, +// Strings returns a nil slice instead. +// If expr is an empty list of string literals, +// returns a non-nil empty slice. +// (this allows differentiating between these two cases) +func Strings(expr Expr) []string { + list, ok := expr.(*ListExpr) + if !ok { + return nil + } + all := []string{} // not nil + for _, l := range list.List { + str, ok := l.(*StringExpr) + if !ok { + return nil + } + all = append(all, str.Value) + } + return all +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/syntax.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/syntax.go new file mode 100644 index 0000000000000000000000000000000000000000..3a1fbd717d082120842ce2f7df1236c885376d48 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/syntax.go @@ -0,0 +1,393 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package build implements parsing and printing of BUILD files. +package build + +// Syntax data structure definitions. + +import ( + "strings" + "unicode/utf8" +) + +// A Position describes the position between two bytes of input. +type Position struct { + Line int // line in input (starting at 1) + LineRune int // rune in line (starting at 1) + Byte int // byte in input (starting at 0) +} + +// add returns the position at the end of s, assuming it starts at p. +func (p Position) add(s string) Position { + p.Byte += len(s) + if n := strings.Count(s, "\n"); n > 0 { + p.Line += n + s = s[strings.LastIndex(s, "\n")+1:] + p.LineRune = 1 + } + p.LineRune += utf8.RuneCountInString(s) + return p +} + +// An Expr represents an input element. +type Expr interface { + // Span returns the start and end position of the expression, + // excluding leading or trailing comments. + Span() (start, end Position) + + // Comment returns the comments attached to the expression. + // This method would normally be named 'Comments' but that + // would interfere with embedding a type of the same name. + Comment() *Comments +} + +// A Comment represents a single # comment. +type Comment struct { + Start Position + Token string // without trailing newline + Suffix bool // an end of line (not whole line) comment +} + +// Comments collects the comments associated with an expression. +type Comments struct { + Before []Comment // whole-line comments before this expression + Suffix []Comment // end-of-line comments after this expression + + // For top-level expressions only, After lists whole-line + // comments following the expression. + After []Comment +} + +// Comment returns the receiver. This isn't useful by itself, but +// a Comments struct is embedded into all the expression +// implementation types, and this gives each of those a Comment +// method to satisfy the Expr interface. +func (c *Comments) Comment() *Comments { + return c +} + +// A File represents an entire BUILD file. +type File struct { + Path string // file path, relative to workspace directory + Comments + Stmt []Expr +} + +func (x *File) Span() (start, end Position) { + if len(x.Stmt) == 0 { + return + } + start, _ = x.Stmt[0].Span() + _, end = x.Stmt[len(x.Stmt)-1].Span() + return start, end +} + +// A CommentBlock represents a top-level block of comments separate +// from any rule. +type CommentBlock struct { + Comments + Start Position +} + +func (x *CommentBlock) Span() (start, end Position) { + return x.Start, x.Start +} + +// A PythonBlock represents a blob of Python code, typically a def or for loop. +type PythonBlock struct { + Comments + Start Position + Token string // raw Python code, including final newline +} + +func (x *PythonBlock) Span() (start, end Position) { + return x.Start, x.Start.add(x.Token) +} + +// A LiteralExpr represents a literal identifier or number. +type LiteralExpr struct { + Comments + Start Position + Token string // identifier token +} + +func (x *LiteralExpr) Span() (start, end Position) { + return x.Start, x.Start.add(x.Token) +} + +// A StringExpr represents a single literal string. +type StringExpr struct { + Comments + Start Position + Value string // string value (decoded) + TripleQuote bool // triple quote output + End Position + + // To allow specific formatting of string literals, + // at least within our requirements, record the + // preferred form of Value. This field is a hint: + // it is only used if it is a valid quoted form for Value. + Token string +} + +func (x *StringExpr) Span() (start, end Position) { + return x.Start, x.End +} + +// An End represents the end of a parenthesized or bracketed expression. +// It is a place to hang comments. +type End struct { + Comments + Pos Position +} + +func (x *End) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// A CallExpr represents a function call expression: X(List). +type CallExpr struct { + Comments + X Expr + ListStart Position // position of ( + List []Expr + End // position of ) + ForceCompact bool // force compact (non-multiline) form when printing + ForceMultiLine bool // force multiline form when printing +} + +func (x *CallExpr) Span() (start, end Position) { + start, _ = x.X.Span() + return start, x.End.Pos.add(")") +} + +// A DotExpr represents a field selector: X.Name. +type DotExpr struct { + Comments + X Expr + Dot Position + NamePos Position + Name string +} + +func (x *DotExpr) Span() (start, end Position) { + start, _ = x.X.Span() + return start, x.NamePos.add(x.Name) +} + +// A ListForExpr represents a list comprehension expression: [X for ... if ...]. +type ListForExpr struct { + Comments + ForceMultiLine bool // split expression across multiple lines + Brack string // "", "()", or "[]" + Start Position + X Expr + For []*ForClause + If []*IfClause + End +} + +func (x *ListForExpr) Span() (start, end Position) { + return x.Start, x.End.Pos.add("]") +} + +// A ForClause represents a for clause in a list comprehension: for Var in Expr. +type ForClause struct { + Comments + For Position + Var []Expr + In Position + Expr Expr +} + +func (x *ForClause) Span() (start, end Position) { + _, end = x.Expr.Span() + return x.For, end +} + +// An IfClause represents an if clause in a list comprehension: if Cond. +type IfClause struct { + Comments + If Position + Cond Expr +} + +func (x *IfClause) Span() (start, end Position) { + _, end = x.Cond.Span() + return x.If, end +} + +// A KeyValueExpr represents a dictionary entry: Key: Value. +type KeyValueExpr struct { + Comments + Key Expr + Colon Position + Value Expr +} + +func (x *KeyValueExpr) Span() (start, end Position) { + start, _ = x.Key.Span() + _, end = x.Value.Span() + return start, end +} + +// A DictExpr represents a dictionary literal: { List }. +type DictExpr struct { + Comments + Start Position + List []Expr // all *KeyValueExprs + Comma Position // position of trailing comma, if any + End + ForceMultiLine bool // force multiline form when printing +} + +func (x *DictExpr) Span() (start, end Position) { + return x.Start, x.End.Pos.add("}") +} + +// A ListExpr represents a list literal: [ List ]. +type ListExpr struct { + Comments + Start Position + List []Expr + Comma Position // position of trailing comma, if any + End + ForceMultiLine bool // force multiline form when printing +} + +func (x *ListExpr) Span() (start, end Position) { + return x.Start, x.End.Pos.add("]") +} + +// A TupleExpr represents a tuple literal: (List) +type TupleExpr struct { + Comments + Start Position + List []Expr + Comma Position // position of trailing comma, if any + End + ForceCompact bool // force compact (non-multiline) form when printing + ForceMultiLine bool // force multiline form when printing +} + +func (x *TupleExpr) Span() (start, end Position) { + return x.Start, x.End.Pos.add(")") +} + +// A UnaryExpr represents a unary expression: Op X. +type UnaryExpr struct { + Comments + OpStart Position + Op string + X Expr +} + +func (x *UnaryExpr) Span() (start, end Position) { + _, end = x.X.Span() + return x.OpStart, end +} + +// A BinaryExpr represents a binary expression: X Op Y. +type BinaryExpr struct { + Comments + X Expr + OpStart Position + Op string + LineBreak bool // insert line break between Op and Y + Y Expr +} + +func (x *BinaryExpr) Span() (start, end Position) { + start, _ = x.X.Span() + _, end = x.Y.Span() + return start, end +} + +// A ParenExpr represents a parenthesized expression: (X). +type ParenExpr struct { + Comments + Start Position + X Expr + End + ForceMultiLine bool // insert line break after opening ( and before closing ) +} + +func (x *ParenExpr) Span() (start, end Position) { + return x.Start, x.End.Pos.add(")") +} + +// A SliceExpr represents a slice expression: X[Y:Z]. +type SliceExpr struct { + Comments + X Expr + SliceStart Position + Y Expr + Colon Position + Z Expr + End Position +} + +func (x *SliceExpr) Span() (start, end Position) { + start, _ = x.X.Span() + return start, x.End +} + +// An IndexExpr represents an index expression: X[Y]. +type IndexExpr struct { + Comments + X Expr + IndexStart Position + Y Expr + End Position +} + +func (x *IndexExpr) Span() (start, end Position) { + start, _ = x.X.Span() + return start, x.End +} + +// A LambdaExpr represents a lambda expression: lambda Var: Expr. +type LambdaExpr struct { + Comments + Lambda Position + Var []Expr + Colon Position + Expr Expr +} + +func (x *LambdaExpr) Span() (start, end Position) { + _, end = x.Expr.Span() + return x.Lambda, end +} + +// ConditionalExpr represents the conditional: X if TEST else ELSE. +type ConditionalExpr struct { + Comments + Then Expr + IfStart Position + Test Expr + ElseStart Position + Else Expr +} + +// Span returns the start and end position of the expression, +// excluding leading or trailing comments. +func (x *ConditionalExpr) Span() (start, end Position) { + start, _ = x.Then.Span() + _, end = x.Else.Span() + return start, end +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/tables.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/tables.go new file mode 100644 index 0000000000000000000000000000000000000000..3f6ccde6c02414a63eeaa2009afb42158ecb7daf --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/tables.go @@ -0,0 +1,173 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Tables about what Buildifier can and cannot edit. +// Perhaps eventually this will be +// derived from the BUILD encyclopedia. + +package build + +// A named argument to a rule call is considered to have a value +// that can be treated as a label or list of labels if the name +// is one of these names. There is a separate blacklist for +// rule-specific exceptions. +var isLabelArg = map[string]bool{ + "app_target": true, + "appdir": true, + "base_package": true, + "build_deps": true, + "cc_deps": true, + "ccdeps": true, + "common_deps": true, + "compile_deps": true, + "compiler": true, + "data": true, + "default_visibility": true, + "dep": true, + "deps": true, + "deps_java": true, + "dont_depend_on": true, + "env_deps": true, + "envscripts": true, + "exported_deps": true, + "exports": true, + "externs_list": true, + "files": true, + "globals": true, + "implementation": true, + "implements": true, + "includes": true, + "interface": true, + "jar": true, + "jars": true, + "javadeps": true, + "lib_deps": true, + "library": true, + "malloc": true, + "model": true, + "mods": true, + "module_deps": true, + "module_target": true, + "of": true, + "plugins": true, + "proto_deps": true, + "proto_target": true, + "protos": true, + "resource": true, + "resources": true, + "runtime_deps": true, + "scope": true, + "shared_deps": true, + "similar_deps": true, + "source_jar": true, + "src": true, + "srcs": true, + "stripped_targets": true, + "suites": true, + "swigdeps": true, + "target": true, + "target_devices": true, + "target_platforms": true, + "template": true, + "test": true, + "tests": true, + "tests_deps": true, + "tool": true, + "tools": true, + "visibility": true, +} + +// labelBlacklist is the list of call arguments that cannot be +// shortened, because they are not interpreted using the same +// rules as for other labels. +var labelBlacklist = map[string]bool{ + // Shortening this can cause visibility checks to fail. + "package_group.includes": true, +} + +// A named argument to a rule call is considered to be a sortable list +// if the name is one of these names. There is a separate blacklist for +// rule-specific exceptions. +var isSortableListArg = map[string]bool{ + "cc_deps": true, + "common_deps": true, + "compile_deps": true, + "configs": true, + "constraints": true, + "data": true, + "default_visibility": true, + "deps": true, + "deps_java": true, + "exported_deps": true, + "exports": true, + "filegroups": true, + "files": true, + "hdrs": true, + "imports": true, + "includes": true, + "inherits": true, + "javadeps": true, + "lib_deps": true, + "module_deps": true, + "out": true, + "outs": true, + "packages": true, + "plugin_modules": true, + "proto_deps": true, + "protos": true, + "pubs": true, + "resources": true, + "runtime_deps": true, + "shared_deps": true, + "similar_deps": true, + "srcs": true, + "swigdeps": true, + "swig_includes": true, + "tags": true, + "tests": true, + "tools": true, + "to_start_extensions": true, + "visibility": true, +} + +// sortableBlacklist records specific rule arguments that must not be reordered. +var sortableBlacklist = map[string]bool{ + "genrule.outs": true, + "genrule.srcs": true, +} + +// sortableWhitelist records specific rule arguments that are guaranteed +// to be reorderable, because bazel re-sorts the list itself after reading the BUILD file. +var sortableWhitelist = map[string]bool{ + "cc_inc_library.hdrs": true, + "cc_library.hdrs": true, + "java_library.srcs": true, + "java_library.resources": true, + "java_binary.srcs": true, + "java_binary.resources": true, + "java_test.srcs": true, + "java_test.resources": true, + "java_library.constraints": true, + "java_import.constraints": true, +} + +// OverrideTables allows a user of the build package to override the special-case rules. +func OverrideTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhitelist map[string]bool) { + isLabelArg = labelArg + labelBlacklist = blacklist + isSortableListArg = sortableListArg + sortableBlacklist = sortBlacklist + sortableWhitelist = sortWhitelist +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/walk.go b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/walk.go new file mode 100644 index 0000000000000000000000000000000000000000..120b12679437f37d3a8e1bf31c0f3e4d2d8b9392 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/walk.go @@ -0,0 +1,128 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package build + +// Walk walks the expression tree v, calling f on all subexpressions +// in a preorder traversal. +// +// The stk argument is the stack of expressions in the recursion above x, +// from outermost to innermost. +// +func Walk(v Expr, f func(x Expr, stk []Expr)) { + var stack []Expr + walk1(&v, &stack, func(x Expr, stk []Expr) Expr { + f(x, stk) + return nil + }) +} + +// WalkAndUpdate walks the expression tree v, calling f on all subexpressions +// in a preorder traversal. If f returns a non-nil value, the tree is mutated. +// The new value replaces the old one. +// +// The stk argument is the stack of expressions in the recursion above x, +// from outermost to innermost. +// +func Edit(v Expr, f func(x Expr, stk []Expr) Expr) Expr { + var stack []Expr + return walk1(&v, &stack, f) +} + +// walk1 is the actual implementation of Walk and WalkAndUpdate. +// It has the same signature and meaning as Walk, +// except that it maintains in *stack the current stack +// of nodes. Using a pointer to a slice here ensures that +// as the stack grows and shrinks the storage can be +// reused for the next growth. +func walk1(v *Expr, stack *[]Expr, f func(x Expr, stk []Expr) Expr) Expr { + if v == nil { + return nil + } + + if res := f(*v, *stack); res != nil { + *v = res + } + *stack = append(*stack, *v) + switch v := (*v).(type) { + case *File: + for _, stmt := range v.Stmt { + walk1(&stmt, stack, f) + } + case *DotExpr: + walk1(&v.X, stack, f) + case *IndexExpr: + walk1(&v.X, stack, f) + walk1(&v.Y, stack, f) + case *KeyValueExpr: + walk1(&v.Key, stack, f) + walk1(&v.Value, stack, f) + case *SliceExpr: + walk1(&v.X, stack, f) + if v.Y != nil { + walk1(&v.Y, stack, f) + } + if v.Z != nil { + walk1(&v.Z, stack, f) + } + case *ParenExpr: + walk1(&v.X, stack, f) + case *UnaryExpr: + walk1(&v.X, stack, f) + case *BinaryExpr: + walk1(&v.X, stack, f) + walk1(&v.Y, stack, f) + case *LambdaExpr: + for i := range v.Var { + walk1(&v.Var[i], stack, f) + } + walk1(&v.Expr, stack, f) + case *CallExpr: + walk1(&v.X, stack, f) + for i := range v.List { + walk1(&v.List[i], stack, f) + } + case *ListExpr: + for i := range v.List { + walk1(&v.List[i], stack, f) + } + case *TupleExpr: + for i := range v.List { + walk1(&v.List[i], stack, f) + } + case *DictExpr: + for i := range v.List { + walk1(&v.List[i], stack, f) + } + case *ListForExpr: + walk1(&v.X, stack, f) + for _, c := range v.For { + for j := range c.Var { + walk1(&c.Var[j], stack, f) + } + walk1(&c.Expr, stack, f) + } + for _, c := range v.If { + walk1(&c.Cond, stack, f) + } + case *ConditionalExpr: + walk1(&v.Then, stack, f) + walk1(&v.Test, stack, f) + walk1(&v.Else, stack, f) + } + *stack = (*stack)[:len(*stack)-1] + return *v +} diff --git a/repo-infra/vendor/github.com/bazelbuild/buildifier/core/y.output b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/y.output new file mode 100644 index 0000000000000000000000000000000000000000..c24236cfd6c287e3d604690300a95d6bbb8d9a17 --- /dev/null +++ b/repo-infra/vendor/github.com/bazelbuild/buildifier/core/y.output @@ -0,0 +1,2895 @@ + +state 0 + $accept: .file $end + stmts: . (2) + + . reduce 2 (src line 156) + + stmts goto 2 + file goto 1 + +state 1 + $accept: file.$end + + $end accept + . error + + +state 2 + file: stmts._EOF + stmts: stmts.stmt comma_opt semi_opt + stmts: stmts.'\n' + stmts: stmts._COMMENT + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _COMMENT shift 6 + _EOF shift 3 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _PYTHON shift 8 + _STRING shift 20 + '\n' shift 5 + . error + + expr goto 7 + ident goto 9 + stmt goto 4 + string goto 19 + strings goto 10 + +state 3 + file: stmts _EOF. (1) + + . reduce 1 (src line 149) + + +state 4 + stmts: stmts stmt.comma_opt semi_opt + comma_opt: . (48) + + ',' shift 22 + . reduce 48 (src line 432) + + comma_opt goto 21 + +state 5 + stmts: stmts '\n'. (4) + + . reduce 4 (src line 192) + + +state 6 + stmts: stmts _COMMENT. (5) + + . reduce 5 (src line 198) + + +state 7 + stmt: expr. (6) + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 6 (src line 211) + + +state 8 + stmt: _PYTHON. (7) + + . reduce 7 (src line 213) + + +state 9 + expr: ident. (10) + + . reduce 10 (src line 221) + + +state 10 + expr: strings. (11) + strings: strings.string + + _STRING shift 20 + . reduce 11 (src line 223) + + string goto 45 + +state 11 + expr: '['.exprs_opt ']' + expr: '['.expr for_clauses if_clauses_opt ']' + exprs_opt: . (57) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 57 (src line 476) + + expr goto 47 + exprs goto 48 + exprs_opt goto 46 + ident goto 9 + string goto 19 + strings goto 10 + +state 12 + expr: '('.expr for_clauses if_clauses_opt ')' + expr: '('.exprs_opt ')' + exprs_opt: . (57) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 57 (src line 476) + + expr goto 49 + exprs goto 48 + exprs_opt goto 50 + ident goto 9 + string goto 19 + strings goto 10 + +state 13 + expr: '{'.keyvalue for_clauses if_clauses_opt '}' + expr: '{'.keyvalues_opt '}' + keyvalues_opt: . (53) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 53 (src line 457) + + expr goto 53 + ident goto 9 + keyvalue goto 51 + keyvalues goto 54 + keyvalues_opt goto 52 + string goto 19 + strings goto 10 + +state 14 + expr: _LAMBDA.exprs ':' expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 56 + exprs goto 55 + ident goto 9 + string goto 19 + strings goto 10 + +state 15 + expr: '-'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 57 + ident goto 9 + string goto 19 + strings goto 10 + +state 16 + expr: _NOT.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 58 + ident goto 9 + string goto 19 + strings goto 10 + +state 17 + expr: '*'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 59 + ident goto 9 + string goto 19 + strings goto 10 + +state 18 + ident: _IDENT. (62) + + . reduce 62 (src line 507) + + +state 19 + strings: string. (60) + + . reduce 60 (src line 497) + + +state 20 + string: _STRING. (59) + + . reduce 59 (src line 485) + + +state 21 + stmts: stmts stmt comma_opt.semi_opt + semi_opt: . (8) + + . reduce 8 (src line 218) + + semi_opt goto 60 + +state 22 + comma_opt: ','. (49) + + . reduce 49 (src line 436) + + +state 23 + expr: expr '.'._IDENT + + _IDENT shift 61 + . error + + +state 24 + expr: expr '('.exprs_opt ')' + expr: expr '('.expr for_clauses if_clauses_opt ')' + exprs_opt: . (57) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 57 (src line 476) + + expr goto 63 + exprs goto 48 + exprs_opt goto 62 + ident goto 9 + string goto 19 + strings goto 10 + +state 25 + expr: expr '['.expr ']' + expr: expr '['.expr_opt ':' expr_opt ']' + expr_opt: . (46) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 46 (src line 422) + + expr goto 64 + expr_opt goto 65 + ident goto 9 + string goto 19 + strings goto 10 + +state 26 + expr: expr '*'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 66 + ident goto 9 + string goto 19 + strings goto 10 + +state 27 + expr: expr '%'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 67 + ident goto 9 + string goto 19 + strings goto 10 + +state 28 + expr: expr '/'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 68 + ident goto 9 + string goto 19 + strings goto 10 + +state 29 + expr: expr '+'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 69 + ident goto 9 + string goto 19 + strings goto 10 + +state 30 + expr: expr '-'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 70 + ident goto 9 + string goto 19 + strings goto 10 + +state 31 + expr: expr '<'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 71 + ident goto 9 + string goto 19 + strings goto 10 + +state 32 + expr: expr '>'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 72 + ident goto 9 + string goto 19 + strings goto 10 + +state 33 + expr: expr _EQ.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 73 + ident goto 9 + string goto 19 + strings goto 10 + +state 34 + expr: expr _LE.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 74 + ident goto 9 + string goto 19 + strings goto 10 + +state 35 + expr: expr _NE.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 75 + ident goto 9 + string goto 19 + strings goto 10 + +state 36 + expr: expr _GE.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 76 + ident goto 9 + string goto 19 + strings goto 10 + +state 37 + expr: expr '='.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 77 + ident goto 9 + string goto 19 + strings goto 10 + +state 38 + expr: expr _ADDEQ.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 78 + ident goto 9 + string goto 19 + strings goto 10 + +state 39 + expr: expr _IN.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 79 + ident goto 9 + string goto 19 + strings goto 10 + +state 40 + expr: expr _NOT._IN expr + + _IN shift 80 + . error + + +state 41 + expr: expr _OR.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 81 + ident goto 9 + string goto 19 + strings goto 10 + +state 42 + expr: expr _AND.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 82 + ident goto 9 + string goto 19 + strings goto 10 + +state 43 + expr: expr _IS.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 83 + ident goto 9 + string goto 19 + strings goto 10 + +state 44 + expr: expr _IF.expr _ELSE expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 84 + ident goto 9 + string goto 19 + strings goto 10 + +state 45 + strings: strings string. (61) + + . reduce 61 (src line 502) + + +state 46 + expr: '[' exprs_opt.']' + + ']' shift 85 + . error + + +state 47 + expr: '[' expr.for_clauses if_clauses_opt ']' + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + exprs: expr. (55) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _FOR shift 88 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 55 (src line 466) + + for_clause goto 87 + for_clauses goto 86 + +state 48 + exprs: exprs.',' expr + exprs_opt: exprs.comma_opt + comma_opt: . (48) + + ',' shift 89 + . reduce 48 (src line 432) + + comma_opt goto 90 + +state 49 + expr: '(' expr.for_clauses if_clauses_opt ')' + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + exprs: expr. (55) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _FOR shift 88 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 55 (src line 466) + + for_clause goto 87 + for_clauses goto 91 + +state 50 + expr: '(' exprs_opt.')' + + ')' shift 92 + . error + + +state 51 + expr: '{' keyvalue.for_clauses if_clauses_opt '}' + keyvalues: keyvalue. (51) + + _FOR shift 88 + . reduce 51 (src line 447) + + for_clause goto 87 + for_clauses goto 93 + +state 52 + expr: '{' keyvalues_opt.'}' + + '}' shift 94 + . error + + +state 53 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + keyvalue: expr.':' expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + ':' shift 95 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . error + + +state 54 + keyvalues: keyvalues.',' keyvalue + keyvalues_opt: keyvalues.comma_opt + comma_opt: . (48) + + ',' shift 96 + . reduce 48 (src line 432) + + comma_opt goto 97 + +state 55 + expr: _LAMBDA exprs.':' expr + exprs: exprs.',' expr + + ',' shift 99 + ':' shift 98 + . error + + +state 56 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + exprs: expr. (55) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 55 (src line 466) + + +state 57 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: '-' expr. (24) + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + . reduce 24 (src line 383) + + +state 58 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: _NOT expr. (25) + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + . reduce 25 (src line 384) + + +state 59 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: '*' expr. (26) + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + . reduce 26 (src line 385) + + +state 60 + stmts: stmts stmt comma_opt semi_opt. (3) + semi_opt: semi_opt.';' + + ';' shift 100 + . reduce 3 (src line 161) + + +state 61 + expr: expr '.' _IDENT. (18) + + . reduce 18 (src line 316) + + +state 62 + expr: expr '(' exprs_opt.')' + + ')' shift 101 + . error + + +state 63 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr '(' expr.for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + exprs: expr. (55) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _FOR shift 88 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 55 (src line 466) + + for_clause goto 87 + for_clauses goto 102 + +state 64 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr '[' expr.']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + expr_opt: expr. (47) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + ']' shift 103 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 47 (src line 426) + + +state 65 + expr: expr '[' expr_opt.':' expr_opt ']' + + ':' shift 104 + . error + + +state 66 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr '*' expr. (27) + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '(' shift 24 + '.' shift 23 + '[' shift 25 + . reduce 27 (src line 386) + + +state 67 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr '%' expr. (28) + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '(' shift 24 + '.' shift 23 + '[' shift 25 + . reduce 28 (src line 387) + + +state 68 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr '/' expr. (29) + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '(' shift 24 + '.' shift 23 + '[' shift 25 + . reduce 29 (src line 388) + + +state 69 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr '+' expr. (30) + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 30 (src line 389) + + +state 70 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr '-' expr. (31) + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 31 (src line 390) + + +state 71 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr '<' expr. (32) + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 32 (src line 391) + + +state 72 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr '>' expr. (33) + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 33 (src line 392) + + +state 73 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr _EQ expr. (34) + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 34 (src line 393) + + +state 74 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr _LE expr. (35) + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 35 (src line 394) + + +state 75 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr _NE expr. (36) + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 36 (src line 395) + + +state 76 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr _GE expr. (37) + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '[' shift 25 + . reduce 37 (src line 396) + + +state 77 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr '=' expr. (38) + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 38 (src line 397) + + +state 78 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr _ADDEQ expr. (39) + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 39 (src line 398) + + +state 79 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr _IN expr. (40) + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _LE shift 34 + _NE shift 35 + _OR shift 41 + . reduce 40 (src line 399) + + +state 80 + expr: expr _NOT _IN.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 105 + ident goto 9 + string goto 19 + strings goto 10 + +state 81 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr _OR expr. (42) + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _LE shift 34 + _NE shift 35 + . reduce 42 (src line 401) + + +state 82 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr _AND expr. (43) + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _EQ shift 33 + _GE shift 36 + _LE shift 34 + _NE shift 35 + . reduce 43 (src line 402) + + +state 83 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr _IS expr. (44) + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _LE shift 34 + _NE shift 35 + _OR shift 41 + . reduce 44 (src line 403) + + +state 84 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + expr: expr _IF expr._ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _ELSE shift 106 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . error + + +state 85 + expr: '[' exprs_opt ']'. (12) + + . reduce 12 (src line 236) + + +state 86 + expr: '[' expr for_clauses.if_clauses_opt ']' + for_clauses: for_clauses.for_clause + if_clauses_opt: . (69) + + _FOR shift 88 + . reduce 69 (src line 552) + + for_clause goto 108 + if_clauses_opt goto 107 + +state 87 + for_clauses: for_clause. (67) + + . reduce 67 (src line 543) + + +state 88 + for_clause: _FOR.idents _IN expr + for_clause: _FOR.'(' idents ')' _IN expr + + '(' shift 110 + _IDENT shift 18 + . error + + ident goto 111 + idents goto 109 + +state 89 + comma_opt: ','. (49) + exprs: exprs ','.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 49 (src line 436) + + expr goto 112 + ident goto 9 + string goto 19 + strings goto 10 + +state 90 + exprs_opt: exprs comma_opt. (58) + + . reduce 58 (src line 480) + + +state 91 + expr: '(' expr for_clauses.if_clauses_opt ')' + for_clauses: for_clauses.for_clause + if_clauses_opt: . (69) + + _FOR shift 88 + . reduce 69 (src line 552) + + for_clause goto 108 + if_clauses_opt goto 113 + +state 92 + expr: '(' exprs_opt ')'. (17) + + . reduce 17 (src line 295) + + +state 93 + expr: '{' keyvalue for_clauses.if_clauses_opt '}' + for_clauses: for_clauses.for_clause + if_clauses_opt: . (69) + + _FOR shift 88 + . reduce 69 (src line 552) + + for_clause goto 108 + if_clauses_opt goto 114 + +state 94 + expr: '{' keyvalues_opt '}'. (16) + + . reduce 16 (src line 285) + + +state 95 + keyvalue: expr ':'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 115 + ident goto 9 + string goto 19 + strings goto 10 + +state 96 + comma_opt: ','. (49) + keyvalues: keyvalues ','.keyvalue + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 49 (src line 436) + + expr goto 53 + ident goto 9 + keyvalue goto 116 + string goto 19 + strings goto 10 + +state 97 + keyvalues_opt: keyvalues comma_opt. (54) + + . reduce 54 (src line 461) + + +state 98 + expr: _LAMBDA exprs ':'.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 117 + ident goto 9 + string goto 19 + strings goto 10 + +state 99 + exprs: exprs ','.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 112 + ident goto 9 + string goto 19 + strings goto 10 + +state 100 + semi_opt: semi_opt ';'. (9) + + . reduce 9 (src line 219) + + +state 101 + expr: expr '(' exprs_opt ')'. (19) + + . reduce 19 (src line 325) + + +state 102 + expr: expr '(' expr for_clauses.if_clauses_opt ')' + for_clauses: for_clauses.for_clause + if_clauses_opt: . (69) + + _FOR shift 88 + . reduce 69 (src line 552) + + for_clause goto 108 + if_clauses_opt goto 118 + +state 103 + expr: expr '[' expr ']'. (21) + + . reduce 21 (src line 354) + + +state 104 + expr: expr '[' expr_opt ':'.expr_opt ']' + expr_opt: . (46) + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . reduce 46 (src line 422) + + expr goto 120 + expr_opt goto 119 + ident goto 9 + string goto 19 + strings goto 10 + +state 105 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr _NOT _IN expr. (41) + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _LE shift 34 + _NE shift 35 + _OR shift 41 + . reduce 41 (src line 400) + + +state 106 + expr: expr _IF expr _ELSE.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 121 + ident goto 9 + string goto 19 + strings goto 10 + +state 107 + expr: '[' expr for_clauses if_clauses_opt.']' + if_clauses_opt: if_clauses_opt._IF expr + + ']' shift 122 + _IF shift 123 + . error + + +state 108 + for_clauses: for_clauses for_clause. (68) + + . reduce 68 (src line 548) + + +state 109 + idents: idents.',' ident + for_clause: _FOR idents._IN expr + + ',' shift 124 + _IN shift 125 + . error + + +state 110 + for_clause: _FOR '('.idents ')' _IN expr + + _IDENT shift 18 + . error + + ident goto 111 + idents goto 126 + +state 111 + idents: ident. (63) + + . reduce 63 (src line 513) + + +state 112 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + exprs: exprs ',' expr. (56) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 56 (src line 471) + + +state 113 + expr: '(' expr for_clauses if_clauses_opt.')' + if_clauses_opt: if_clauses_opt._IF expr + + ')' shift 127 + _IF shift 123 + . error + + +state 114 + expr: '{' keyvalue for_clauses if_clauses_opt.'}' + if_clauses_opt: if_clauses_opt._IF expr + + '}' shift 128 + _IF shift 123 + . error + + +state 115 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + keyvalue: expr ':' expr. (50) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 50 (src line 438) + + +state 116 + keyvalues: keyvalues ',' keyvalue. (52) + + . reduce 52 (src line 452) + + +state 117 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: _LAMBDA exprs ':' expr. (23) + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 23 (src line 374) + + +state 118 + expr: expr '(' expr for_clauses if_clauses_opt.')' + if_clauses_opt: if_clauses_opt._IF expr + + ')' shift 129 + _IF shift 123 + . error + + +state 119 + expr: expr '[' expr_opt ':' expr_opt.']' + + ']' shift 130 + . error + + +state 120 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + expr_opt: expr. (47) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IF shift 44 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 47 (src line 426) + + +state 121 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + expr: expr _IF expr _ELSE expr. (45) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '>' shift 32 + '[' shift 25 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 45 (src line 411) + + +state 122 + expr: '[' expr for_clauses if_clauses_opt ']'. (13) + + . reduce 13 (src line 246) + + +state 123 + if_clauses_opt: if_clauses_opt _IF.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 131 + ident goto 9 + string goto 19 + strings goto 10 + +state 124 + idents: idents ','.ident + + _IDENT shift 18 + . error + + ident goto 132 + +state 125 + for_clause: _FOR idents _IN.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 133 + ident goto 9 + string goto 19 + strings goto 10 + +state 126 + idents: idents.',' ident + for_clause: _FOR '(' idents.')' _IN expr + + ')' shift 134 + ',' shift 124 + . error + + +state 127 + expr: '(' expr for_clauses if_clauses_opt ')'. (14) + + . reduce 14 (src line 259) + + +state 128 + expr: '{' keyvalue for_clauses if_clauses_opt '}'. (15) + + . reduce 15 (src line 272) + + +state 129 + expr: expr '(' expr for_clauses if_clauses_opt ')'. (20) + + . reduce 20 (src line 336) + + +state 130 + expr: expr '[' expr_opt ':' expr_opt ']'. (22) + + . reduce 22 (src line 363) + + +state 131 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + if_clauses_opt: if_clauses_opt _IF expr. (70) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 70 (src line 556) + + +state 132 + idents: idents ',' ident. (64) + + . reduce 64 (src line 518) + + +state 133 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + for_clause: _FOR idents _IN expr. (65) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 65 (src line 523) + + +state 134 + for_clause: _FOR '(' idents ')'._IN expr + + _IN shift 135 + . error + + +state 135 + for_clause: _FOR '(' idents ')' _IN.expr + + '(' shift 12 + '*' shift 17 + '-' shift 15 + '[' shift 11 + '{' shift 13 + _IDENT shift 18 + _LAMBDA shift 14 + _NOT shift 16 + _STRING shift 20 + . error + + expr goto 136 + ident goto 9 + string goto 19 + strings goto 10 + +state 136 + expr: expr.'.' _IDENT + expr: expr.'(' exprs_opt ')' + expr: expr.'(' expr for_clauses if_clauses_opt ')' + expr: expr.'[' expr ']' + expr: expr.'[' expr_opt ':' expr_opt ']' + expr: expr.'*' expr + expr: expr.'%' expr + expr: expr.'/' expr + expr: expr.'+' expr + expr: expr.'-' expr + expr: expr.'<' expr + expr: expr.'>' expr + expr: expr._EQ expr + expr: expr._LE expr + expr: expr._NE expr + expr: expr._GE expr + expr: expr.'=' expr + expr: expr._ADDEQ expr + expr: expr._IN expr + expr: expr._NOT _IN expr + expr: expr._OR expr + expr: expr._AND expr + expr: expr._IS expr + expr: expr._IF expr _ELSE expr + for_clause: _FOR '(' idents ')' _IN expr. (66) + + '%' shift 27 + '(' shift 24 + '*' shift 26 + '+' shift 29 + '-' shift 30 + '.' shift 23 + '/' shift 28 + '<' shift 31 + '=' shift 37 + '>' shift 32 + '[' shift 25 + _ADDEQ shift 38 + _AND shift 42 + _EQ shift 33 + _GE shift 36 + _IN shift 39 + _IS shift 43 + _LE shift 34 + _NE shift 35 + _NOT shift 40 + _OR shift 41 + . reduce 66 (src line 533) + + +44 terminals, 20 nonterminals +71 grammar rules, 137/2000 states +0 shift/reduce, 0 reduce/reduce conflicts reported +69 working sets used +memory: parser 241/30000 +96 extra closures +927 shift entries, 1 exceptions +78 goto entries +120 entries saved by goto default +Optimizer space used: output 542/30000 +542 table entries, 174 zero +maximum spread: 44, maximum offset: 135 diff --git a/repo-infra/vendor/github.com/golang/glog/LICENSE b/repo-infra/vendor/github.com/golang/glog/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..37ec93a14fdcd0d6e525d97c0cfa6b314eaa98d8 --- /dev/null +++ b/repo-infra/vendor/github.com/golang/glog/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/repo-infra/vendor/github.com/golang/glog/README b/repo-infra/vendor/github.com/golang/glog/README new file mode 100644 index 0000000000000000000000000000000000000000..5f9c11485e037e27a8f96a5cb5fcc131afc8254f --- /dev/null +++ b/repo-infra/vendor/github.com/golang/glog/README @@ -0,0 +1,44 @@ +glog +==== + +Leveled execution logs for Go. + +This is an efficient pure Go implementation of leveled logs in the +manner of the open source C++ package + http://code.google.com/p/google-glog + +By binding methods to booleans it is possible to use the log package +without paying the expense of evaluating the arguments to the log. +Through the -vmodule flag, the package also provides fine-grained +control over logging at the file level. + +The comment from glog.go introduces the ideas: + + Package glog implements logging analogous to the Google-internal + C++ INFO/ERROR/V setup. It provides functions Info, Warning, + Error, Fatal, plus formatting variants such as Infof. It + also provides V-style logging controlled by the -v and + -vmodule=file=2 flags. + + Basic examples: + + glog.Info("Prepare to repel boarders") + + glog.Fatalf("Initialization failed: %s", err) + + See the documentation for the V function for an explanation + of these examples: + + if glog.V(2) { + glog.Info("Starting transaction...") + } + + glog.V(2).Infoln("Processed", nItems, "elements") + + +The repository contains an open source version of the log package +used inside Google. The master copy of the source lives inside +Google, not here. The code in this repo is for export only and is not itself +under development. Feature requests will be ignored. + +Send bug reports to golang-nuts@googlegroups.com. diff --git a/repo-infra/vendor/github.com/golang/glog/glog.go b/repo-infra/vendor/github.com/golang/glog/glog.go new file mode 100644 index 0000000000000000000000000000000000000000..3e63fffd5ecb1b313b0d2283d19bf10b8b59a29b --- /dev/null +++ b/repo-infra/vendor/github.com/golang/glog/glog.go @@ -0,0 +1,1177 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. +// It provides functions Info, Warning, Error, Fatal, plus formatting variants such as +// Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. +// +// Basic examples: +// +// glog.Info("Prepare to repel boarders") +// +// glog.Fatalf("Initialization failed: %s", err) +// +// See the documentation for the V function for an explanation of these examples: +// +// if glog.V(2) { +// glog.Info("Starting transaction...") +// } +// +// glog.V(2).Infoln("Processed", nItems, "elements") +// +// Log output is buffered and written periodically using Flush. Programs +// should call Flush before exiting to guarantee all log output is written. +// +// By default, all log statements write to files in a temporary directory. +// This package provides several flags that modify this behavior. +// As a result, flag.Parse must be called before any logging is done. +// +// -logtostderr=false +// Logs are written to standard error instead of to files. +// -alsologtostderr=false +// Logs are written to standard error as well as to files. +// -stderrthreshold=ERROR +// Log events at or above this severity are logged to standard +// error as well as to files. +// -log_dir="" +// Log files will be written to this directory instead of the +// default temporary directory. +// +// Other flags provide aids to debugging. +// +// -log_backtrace_at="" +// When set to a file and line number holding a logging statement, +// such as +// -log_backtrace_at=gopherflakes.go:234 +// a stack trace will be written to the Info log whenever execution +// hits that statement. (Unlike with -vmodule, the ".go" must be +// present.) +// -v=0 +// Enable V-leveled logging at the specified level. +// -vmodule="" +// The syntax of the argument is a comma-separated list of pattern=N, +// where pattern is a literal file name (minus the ".go" suffix) or +// "glob" pattern and N is a V level. For instance, +// -vmodule=gopher*=3 +// sets the V level to 3 in all Go files whose names begin "gopher". +// +package glog + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "io" + stdLog "log" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// severity identifies the sort of log: info, warning etc. It also implements +// the flag.Value interface. The -stderrthreshold flag is of type severity and +// should be modified only through the flag.Value interface. The values match +// the corresponding constants in C++. +type severity int32 // sync/atomic int32 + +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. +const ( + infoLog severity = iota + warningLog + errorLog + fatalLog + numSeverity = 4 +) + +const severityChar = "IWEF" + +var severityName = []string{ + infoLog: "INFO", + warningLog: "WARNING", + errorLog: "ERROR", + fatalLog: "FATAL", +} + +// get returns the value of the severity. +func (s *severity) get() severity { + return severity(atomic.LoadInt32((*int32)(s))) +} + +// set sets the value of the severity. +func (s *severity) set(val severity) { + atomic.StoreInt32((*int32)(s), int32(val)) +} + +// String is part of the flag.Value interface. +func (s *severity) String() string { + return strconv.FormatInt(int64(*s), 10) +} + +// Get is part of the flag.Value interface. +func (s *severity) Get() interface{} { + return *s +} + +// Set is part of the flag.Value interface. +func (s *severity) Set(value string) error { + var threshold severity + // Is it a known name? + if v, ok := severityByName(value); ok { + threshold = v + } else { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + threshold = severity(v) + } + logging.stderrThreshold.set(threshold) + return nil +} + +func severityByName(s string) (severity, bool) { + s = strings.ToUpper(s) + for i, name := range severityName { + if name == s { + return severity(i), true + } + } + return 0, false +} + +// OutputStats tracks the number of output lines and bytes written. +type OutputStats struct { + lines int64 + bytes int64 +} + +// Lines returns the number of lines written. +func (s *OutputStats) Lines() int64 { + return atomic.LoadInt64(&s.lines) +} + +// Bytes returns the number of bytes written. +func (s *OutputStats) Bytes() int64 { + return atomic.LoadInt64(&s.bytes) +} + +// Stats tracks the number of lines of output and number of bytes +// per severity level. Values must be read with atomic.LoadInt64. +var Stats struct { + Info, Warning, Error OutputStats +} + +var severityStats = [numSeverity]*OutputStats{ + infoLog: &Stats.Info, + warningLog: &Stats.Warning, + errorLog: &Stats.Error, +} + +// Level is exported because it appears in the arguments to V and is +// the type of the v flag, which can be set programmatically. +// It's a distinct type because we want to discriminate it from logType. +// Variables of type level are only changed under logging.mu. +// The -v flag is read only with atomic ops, so the state of the logging +// module is consistent. + +// Level is treated as a sync/atomic int32. + +// Level specifies a level of verbosity for V logs. *Level implements +// flag.Value; the -v flag is of type Level and should be modified +// only through the flag.Value interface. +type Level int32 + +// get returns the value of the Level. +func (l *Level) get() Level { + return Level(atomic.LoadInt32((*int32)(l))) +} + +// set sets the value of the Level. +func (l *Level) set(val Level) { + atomic.StoreInt32((*int32)(l), int32(val)) +} + +// String is part of the flag.Value interface. +func (l *Level) String() string { + return strconv.FormatInt(int64(*l), 10) +} + +// Get is part of the flag.Value interface. +func (l *Level) Get() interface{} { + return *l +} + +// Set is part of the flag.Value interface. +func (l *Level) Set(value string) error { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(Level(v), logging.vmodule.filter, false) + return nil +} + +// moduleSpec represents the setting of the -vmodule flag. +type moduleSpec struct { + filter []modulePat +} + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(file string) bool { + if m.literal { + return file == m.pattern + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +func (m *moduleSpec) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + var b bytes.Buffer + for i, f := range m.filter { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported. +func (m *moduleSpec) Get() interface{} { + return nil +} + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Syntax: -vmodule=recordio=2,file=1,gfs*=3 +func (m *moduleSpec) Set(value string) error { + var filter []modulePat + for _, pat := range strings.Split(value, ",") { + if len(pat) == 0 { + // Empty strings such as from a trailing comma can be ignored. + continue + } + patLev := strings.Split(pat, "=") + if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { + return errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.Atoi(patLev[1]) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + if v < 0 { + return errors.New("negative value for vmodule level") + } + if v == 0 { + continue // Ignore. It's harmless but no point in paying the overhead. + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(logging.verbosity, filter, true) + return nil +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// traceLocation represents the setting of the -log_backtrace_at flag. +type traceLocation struct { + file string + line int +} + +// isSet reports whether the trace location has been specified. +// logging.mu is held. +func (t *traceLocation) isSet() bool { + return t.line > 0 +} + +// match reports whether the specified file and line matches the trace location. +// The argument file name is the full path, not the basename specified in the flag. +// logging.mu is held. +func (t *traceLocation) match(file string, line int) bool { + if t.line != line { + return false + } + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + return t.file == file +} + +func (t *traceLocation) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + return fmt.Sprintf("%s:%d", t.file, t.line) +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported +func (t *traceLocation) Get() interface{} { + return nil +} + +var errTraceSyntax = errors.New("syntax error: expect file.go:234") + +// Syntax: -log_backtrace_at=gopherflakes.go:234 +// Note that unlike vmodule the file extension is included here. +func (t *traceLocation) Set(value string) error { + if value == "" { + // Unset. + t.line = 0 + t.file = "" + } + fields := strings.Split(value, ":") + if len(fields) != 2 { + return errTraceSyntax + } + file, line := fields[0], fields[1] + if !strings.Contains(file, ".") { + return errTraceSyntax + } + v, err := strconv.Atoi(line) + if err != nil { + return errTraceSyntax + } + if v <= 0 { + return errors.New("negative or zero value for level") + } + logging.mu.Lock() + defer logging.mu.Unlock() + t.line = v + t.file = file + return nil +} + +// flushSyncWriter is the interface satisfied by logging destinations. +type flushSyncWriter interface { + Flush() error + Sync() error + io.Writer +} + +func init() { + flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") + flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flag.Var(&logging.verbosity, "v", "log level for V logs") + flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") + flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") + flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") + + // Default stderrThreshold is ERROR. + logging.stderrThreshold = errorLog + + logging.setVState(0, nil, false) + go logging.flushDaemon() +} + +// Flush flushes all pending log I/O. +func Flush() { + logging.lockAndFlushAll() +} + +// loggingT collects all the global state of the logging setup. +type loggingT struct { + // Boolean flags. Not handled atomically because the flag.Value interface + // does not let us avoid the =true, and that shorthand is necessary for + // compatibility. TODO: does this matter enough to fix? Seems unlikely. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + + // Level flag. Handled atomically. + stderrThreshold severity // The -stderrthreshold flag. + + // freeList is a list of byte buffers, maintained under freeListMu. + freeList *buffer + // freeListMu maintains the free list. It is separate from the main mutex + // so buffers can be grabbed and printed to without holding the main lock, + // for better parallelization. + freeListMu sync.Mutex + + // mu protects the remaining elements of this structure and is + // used to synchronize logging. + mu sync.Mutex + // file holds writer for each of the log types. + file [numSeverity]flushSyncWriter + // pcs is used in V to avoid an allocation when computing the caller's PC. + pcs [1]uintptr + // vmap is a cache of the V Level for each V() call site, identified by PC. + // It is wiped whenever the vmodule flag changes state. + vmap map[uintptr]Level + // filterLength stores the length of the vmodule filter chain. If greater + // than zero, it means vmodule is enabled. It may be read safely + // using sync.LoadInt32, but is only modified under mu. + filterLength int32 + // traceLocation is the state of the -log_backtrace_at flag. + traceLocation traceLocation + // These flags are modified only under lock, although verbosity may be fetched + // safely using atomic.LoadInt32. + vmodule moduleSpec // The state of the -vmodule flag. + verbosity Level // V logging level, the value of the -v flag/ +} + +// buffer holds a byte Buffer for reuse. The zero value is ready for use. +type buffer struct { + bytes.Buffer + tmp [64]byte // temporary byte array for creating headers. + next *buffer +} + +var logging loggingT + +// setVState sets a consistent state for V logging. +// l.mu is held. +func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { + // Turn verbosity off so V will not fire while we are in transition. + logging.verbosity.set(0) + // Ditto for filter length. + atomic.StoreInt32(&logging.filterLength, 0) + + // Set the new filters and wipe the pc->Level map if the filter has changed. + if setFilter { + logging.vmodule.filter = filter + logging.vmap = make(map[uintptr]Level) + } + + // Things are consistent now, so enable filtering and verbosity. + // They are enabled in order opposite to that in V. + atomic.StoreInt32(&logging.filterLength, int32(len(filter))) + logging.verbosity.set(verbosity) +} + +// getBuffer returns a new, ready-to-use buffer. +func (l *loggingT) getBuffer() *buffer { + l.freeListMu.Lock() + b := l.freeList + if b != nil { + l.freeList = b.next + } + l.freeListMu.Unlock() + if b == nil { + b = new(buffer) + } else { + b.next = nil + b.Reset() + } + return b +} + +// putBuffer returns a buffer to the free list. +func (l *loggingT) putBuffer(b *buffer) { + if b.Len() >= 256 { + // Let big buffers die a natural death. + return + } + l.freeListMu.Lock() + b.next = l.freeList + l.freeList = b + l.freeListMu.Unlock() +} + +var timeNow = time.Now // Stubbed out for testing. + +/* +header formats a log header as defined by the C++ implementation. +It returns a buffer containing the formatted header and the user's file and line number. +The depth specifies how many stack frames above lives the source line to be identified in the log message. + +Log lines have this form: + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... +where the fields are defined as follows: + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() + file The file name + line The line number + msg The user-supplied message +*/ +func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { + _, file, line, ok := runtime.Caller(3 + depth) + if !ok { + file = "???" + line = 1 + } else { + slash := strings.LastIndex(file, "/") + if slash >= 0 { + file = file[slash+1:] + } + } + return l.formatHeader(s, file, line), file, line +} + +// formatHeader formats a log header using the provided file name and line number. +func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { + now := timeNow() + if line < 0 { + line = 0 // not a real line number, but acceptable to someDigits + } + if s > fatalLog { + s = infoLog // for safety. + } + buf := l.getBuffer() + + // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. + // It's worth about 3X. Fprintf is hard. + _, month, day := now.Date() + hour, minute, second := now.Clock() + // Lmmdd hh:mm:ss.uuuuuu threadid file:line] + buf.tmp[0] = severityChar[s] + buf.twoDigits(1, int(month)) + buf.twoDigits(3, day) + buf.tmp[5] = ' ' + buf.twoDigits(6, hour) + buf.tmp[8] = ':' + buf.twoDigits(9, minute) + buf.tmp[11] = ':' + buf.twoDigits(12, second) + buf.tmp[14] = '.' + buf.nDigits(6, 15, now.Nanosecond()/1000, '0') + buf.tmp[21] = ' ' + buf.nDigits(7, 22, pid, ' ') // TODO: should be TID + buf.tmp[29] = ' ' + buf.Write(buf.tmp[:30]) + buf.WriteString(file) + buf.tmp[0] = ':' + n := buf.someDigits(1, line) + buf.tmp[n+1] = ']' + buf.tmp[n+2] = ' ' + buf.Write(buf.tmp[:n+3]) + return buf +} + +// Some custom tiny helper functions to print the log header efficiently. + +const digits = "0123456789" + +// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. +func (buf *buffer) twoDigits(i, d int) { + buf.tmp[i+1] = digits[d%10] + d /= 10 + buf.tmp[i] = digits[d%10] +} + +// nDigits formats an n-digit integer at buf.tmp[i], +// padding with pad on the left. +// It assumes d >= 0. +func (buf *buffer) nDigits(n, i, d int, pad byte) { + j := n - 1 + for ; j >= 0 && d > 0; j-- { + buf.tmp[i+j] = digits[d%10] + d /= 10 + } + for ; j >= 0; j-- { + buf.tmp[i+j] = pad + } +} + +// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. +func (buf *buffer) someDigits(i, d int) int { + // Print into the top, then copy down. We know there's space for at least + // a 10-digit number. + j := len(buf.tmp) + for { + j-- + buf.tmp[j] = digits[d%10] + d /= 10 + if d == 0 { + break + } + } + return copy(buf.tmp[i:], buf.tmp[j:]) +} + +func (l *loggingT) println(s severity, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintln(buf, args...) + l.output(s, buf, file, line, false) +} + +func (l *loggingT) print(s severity, args ...interface{}) { + l.printDepth(s, 1, args...) +} + +func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { + buf, file, line := l.header(s, depth) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +func (l *loggingT) printf(s severity, format string, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintf(buf, format, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +// printWithFileLine behaves like print but uses the provided file and line number. If +// alsoLogToStderr is true, the log message always appears on standard error; it +// will also appear in the log file unless --logtostderr is set. +func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { + buf := l.formatHeader(s, file, line) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, alsoToStderr) +} + +// output writes the data to the log files and releases the buffer. +func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { + l.mu.Lock() + if l.traceLocation.isSet() { + if l.traceLocation.match(file, line) { + buf.Write(stacks(false)) + } + } + data := buf.Bytes() + if l.toStderr { + os.Stderr.Write(data) + } else { + if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } + if l.file[s] == nil { + if err := l.createFiles(s); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } + } + switch s { + case fatalLog: + l.file[fatalLog].Write(data) + fallthrough + case errorLog: + l.file[errorLog].Write(data) + fallthrough + case warningLog: + l.file[warningLog].Write(data) + fallthrough + case infoLog: + l.file[infoLog].Write(data) + } + } + if s == fatalLog { + // If we got here via Exit rather than Fatal, print no stacks. + if atomic.LoadUint32(&fatalNoStacks) > 0 { + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(1) + } + // Dump all goroutine stacks before exiting. + // First, make sure we see the trace for the current goroutine on standard error. + // If -logtostderr has been specified, the loop below will do that anyway + // as the first stack in the full dump. + if !l.toStderr { + os.Stderr.Write(stacks(false)) + } + // Write the stack trace for all goroutines to the files. + trace := stacks(true) + logExitFunc = func(error) {} // If we get a write error, we'll still exit below. + for log := fatalLog; log >= infoLog; log-- { + if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. + f.Write(trace) + } + } + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. + } + l.putBuffer(buf) + l.mu.Unlock() + if stats := severityStats[s]; stats != nil { + atomic.AddInt64(&stats.lines, 1) + atomic.AddInt64(&stats.bytes, int64(len(data))) + } +} + +// timeoutFlush calls Flush and returns when it completes or after timeout +// elapses, whichever happens first. This is needed because the hooks invoked +// by Flush may deadlock when glog.Fatal is called from a hook that holds +// a lock. +func timeoutFlush(timeout time.Duration) { + done := make(chan bool, 1) + go func() { + Flush() // calls logging.lockAndFlushAll() + done <- true + }() + select { + case <-done: + case <-time.After(timeout): + fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) + } +} + +// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. +func stacks(all bool) []byte { + // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. + n := 10000 + if all { + n = 100000 + } + var trace []byte + for i := 0; i < 5; i++ { + trace = make([]byte, n) + nbytes := runtime.Stack(trace, all) + if nbytes < len(trace) { + return trace[:nbytes] + } + n *= 2 + } + return trace +} + +// logExitFunc provides a simple mechanism to override the default behavior +// of exiting on error. Used in testing and to guarantee we reach a required exit +// for fatal logs. Instead, exit could be a function rather than a method but that +// would make its use clumsier. +var logExitFunc func(error) + +// exit is called if there is trouble creating or writing log files. +// It flushes the logs and exits the program; there's no point in hanging around. +// l.mu is held. +func (l *loggingT) exit(err error) { + fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) + // If logExitFunc is set, we do that instead of exiting. + if logExitFunc != nil { + logExitFunc(err) + return + } + l.flushAll() + os.Exit(2) +} + +// syncBuffer joins a bufio.Writer to its underlying file, providing access to the +// file's Sync method and providing a wrapper for the Write method that provides log +// file rotation. There are conflicting methods, so the file cannot be embedded. +// l.mu is held for all its methods. +type syncBuffer struct { + logger *loggingT + *bufio.Writer + file *os.File + sev severity + nbytes uint64 // The number of bytes written to this file +} + +func (sb *syncBuffer) Sync() error { + return sb.file.Sync() +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + if sb.nbytes+uint64(len(p)) >= MaxSize { + if err := sb.rotateFile(time.Now()); err != nil { + sb.logger.exit(err) + } + } + n, err = sb.Writer.Write(p) + sb.nbytes += uint64(n) + if err != nil { + sb.logger.exit(err) + } + return +} + +// rotateFile closes the syncBuffer's file and starts a new one. +func (sb *syncBuffer) rotateFile(now time.Time) error { + if sb.file != nil { + sb.Flush() + sb.file.Close() + } + var err error + sb.file, _, err = create(severityName[sb.sev], now) + sb.nbytes = 0 + if err != nil { + return err + } + + sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) + + // Write header. + var buf bytes.Buffer + fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) + fmt.Fprintf(&buf, "Running on machine: %s\n", host) + fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") + n, err := sb.file.Write(buf.Bytes()) + sb.nbytes += uint64(n) + return err +} + +// bufferSize sizes the buffer associated with each log file. It's large +// so that log records can accumulate without the logging thread blocking +// on disk I/O. The flushDaemon will block instead. +const bufferSize = 256 * 1024 + +// createFiles creates all the log files for severity from sev down to infoLog. +// l.mu is held. +func (l *loggingT) createFiles(sev severity) error { + now := time.Now() + // Files are created in decreasing severity order, so as soon as we find one + // has already been created, we can stop. + for s := sev; s >= infoLog && l.file[s] == nil; s-- { + sb := &syncBuffer{ + logger: l, + sev: s, + } + if err := sb.rotateFile(now); err != nil { + return err + } + l.file[s] = sb + } + return nil +} + +const flushInterval = 30 * time.Second + +// flushDaemon periodically flushes the log file buffers. +func (l *loggingT) flushDaemon() { + for _ = range time.NewTicker(flushInterval).C { + l.lockAndFlushAll() + } +} + +// lockAndFlushAll is like flushAll but locks l.mu first. +func (l *loggingT) lockAndFlushAll() { + l.mu.Lock() + l.flushAll() + l.mu.Unlock() +} + +// flushAll flushes all the logs and attempts to "sync" their data to disk. +// l.mu is held. +func (l *loggingT) flushAll() { + // Flush from fatal down, in case there's trouble flushing. + for s := fatalLog; s >= infoLog; s-- { + file := l.file[s] + if file != nil { + file.Flush() // ignore error + file.Sync() // ignore error + } + } +} + +// CopyStandardLogTo arranges for messages written to the Go "log" package's +// default logs to also appear in the Google logs for the named and lower +// severities. Subsequent changes to the standard log's default output location +// or format may break this behavior. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, CopyStandardLogTo panics. +func CopyStandardLogTo(name string) { + sev, ok := severityByName(name) + if !ok { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + } + // Set a log format that captures the user's file and line: + // d.go:23: message + stdLog.SetFlags(stdLog.Lshortfile) + stdLog.SetOutput(logBridge(sev)) +} + +// logBridge provides the Write method that enables CopyStandardLogTo to connect +// Go's standard logs to the logs provided by this package. +type logBridge severity + +// Write parses the standard logging line and passes its components to the +// logger for severity(lb). +func (lb logBridge) Write(b []byte) (n int, err error) { + var ( + file = "???" + line = 1 + text string + ) + // Split "d.go:23: message" into "d.go", "23", and "message". + if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { + text = fmt.Sprintf("bad log format: %s", b) + } else { + file = string(parts[0]) + text = string(parts[2][1:]) // skip leading space + line, err = strconv.Atoi(string(parts[1])) + if err != nil { + text = fmt.Sprintf("bad line number: %s", b) + line = 1 + } + } + // printWithFileLine with alsoToStderr=true, so standard log messages + // always appear on standard error. + logging.printWithFileLine(severity(lb), file, line, true, text) + return len(b), nil +} + +// setV computes and remembers the V level for a given PC +// when vmodule is enabled. +// File pattern matching takes the basename of the file, stripped +// of its .go suffix, and uses filepath.Match, which is a little more +// general than the *? matching used in C++. +// l.mu is held. +func (l *loggingT) setV(pc uintptr) Level { + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range l.vmodule.filter { + if filter.match(file) { + l.vmap[pc] = filter.level + return filter.level + } + } + l.vmap[pc] = 0 + return 0 +} + +// Verbose is a boolean type that implements Infof (like Printf) etc. +// See the documentation of V for more information. +type Verbose bool + +// V reports whether verbosity at the call site is at least the requested level. +// The returned value is a boolean of type Verbose, which implements Info, Infoln +// and Infof. These methods will write to the Info log if called. +// Thus, one may write either +// if glog.V(2) { glog.Info("log this") } +// or +// glog.V(2).Info("log this") +// The second form is shorter but the first is cheaper if logging is off because it does +// not evaluate its arguments. +// +// Whether an individual call to V generates a log record depends on the setting of +// the -v and --vmodule flags; both are off by default. If the level in the call to +// V is at least the value of -v, or of -vmodule for the source file containing the +// call, the V call will log. +func V(level Level) Verbose { + // This function tries hard to be cheap unless there's work to do. + // The fast path is two atomic loads and compares. + + // Here is a cheap but safe test to see if V logging is enabled globally. + if logging.verbosity.get() >= level { + return Verbose(true) + } + + // It's off globally but it vmodule may still be set. + // Here is another cheap but safe test to see if vmodule is enabled. + if atomic.LoadInt32(&logging.filterLength) > 0 { + // Now we need a proper lock to use the logging structure. The pcs field + // is shared so we must lock before accessing it. This is fairly expensive, + // but if V logging is enabled we're slow anyway. + logging.mu.Lock() + defer logging.mu.Unlock() + if runtime.Callers(2, logging.pcs[:]) == 0 { + return Verbose(false) + } + v, ok := logging.vmap[logging.pcs[0]] + if !ok { + v = logging.setV(logging.pcs[0]) + } + return Verbose(v >= level) + } + return Verbose(false) +} + +// Info is equivalent to the global Info function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Info(args ...interface{}) { + if v { + logging.print(infoLog, args...) + } +} + +// Infoln is equivalent to the global Infoln function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infoln(args ...interface{}) { + if v { + logging.println(infoLog, args...) + } +} + +// Infof is equivalent to the global Infof function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infof(format string, args ...interface{}) { + if v { + logging.printf(infoLog, format, args...) + } +} + +// Info logs to the INFO log. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Info(args ...interface{}) { + logging.print(infoLog, args...) +} + +// InfoDepth acts as Info but uses depth to determine which call frame to log. +// InfoDepth(0, "msg") is the same as Info("msg"). +func InfoDepth(depth int, args ...interface{}) { + logging.printDepth(infoLog, depth, args...) +} + +// Infoln logs to the INFO log. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Infoln(args ...interface{}) { + logging.println(infoLog, args...) +} + +// Infof logs to the INFO log. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Infof(format string, args ...interface{}) { + logging.printf(infoLog, format, args...) +} + +// Warning logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Warning(args ...interface{}) { + logging.print(warningLog, args...) +} + +// WarningDepth acts as Warning but uses depth to determine which call frame to log. +// WarningDepth(0, "msg") is the same as Warning("msg"). +func WarningDepth(depth int, args ...interface{}) { + logging.printDepth(warningLog, depth, args...) +} + +// Warningln logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Warningln(args ...interface{}) { + logging.println(warningLog, args...) +} + +// Warningf logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Warningf(format string, args ...interface{}) { + logging.printf(warningLog, format, args...) +} + +// Error logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Error(args ...interface{}) { + logging.print(errorLog, args...) +} + +// ErrorDepth acts as Error but uses depth to determine which call frame to log. +// ErrorDepth(0, "msg") is the same as Error("msg"). +func ErrorDepth(depth int, args ...interface{}) { + logging.printDepth(errorLog, depth, args...) +} + +// Errorln logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Errorln(args ...interface{}) { + logging.println(errorLog, args...) +} + +// Errorf logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Errorf(format string, args ...interface{}) { + logging.printf(errorLog, format, args...) +} + +// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Fatal(args ...interface{}) { + logging.print(fatalLog, args...) +} + +// FatalDepth acts as Fatal but uses depth to determine which call frame to log. +// FatalDepth(0, "msg") is the same as Fatal("msg"). +func FatalDepth(depth int, args ...interface{}) { + logging.printDepth(fatalLog, depth, args...) +} + +// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Fatalln(args ...interface{}) { + logging.println(fatalLog, args...) +} + +// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Fatalf(format string, args ...interface{}) { + logging.printf(fatalLog, format, args...) +} + +// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. +// It allows Exit and relatives to use the Fatal logs. +var fatalNoStacks uint32 + +// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Exit(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.print(fatalLog, args...) +} + +// ExitDepth acts as Exit but uses depth to determine which call frame to log. +// ExitDepth(0, "msg") is the same as Exit("msg"). +func ExitDepth(depth int, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printDepth(fatalLog, depth, args...) +} + +// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +func Exitln(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.println(fatalLog, args...) +} + +// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Exitf(format string, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printf(fatalLog, format, args...) +} diff --git a/repo-infra/vendor/github.com/golang/glog/glog_file.go b/repo-infra/vendor/github.com/golang/glog/glog_file.go new file mode 100644 index 0000000000000000000000000000000000000000..65075d281110cf445418640936d7c08d315512db --- /dev/null +++ b/repo-infra/vendor/github.com/golang/glog/glog_file.go @@ -0,0 +1,124 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// File I/O for logs. + +package glog + +import ( + "errors" + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + "sync" + "time" +) + +// MaxSize is the maximum size of a log file in bytes. +var MaxSize uint64 = 1024 * 1024 * 1800 + +// logDirs lists the candidate directories for new log files. +var logDirs []string + +// If non-empty, overrides the choice of directory in which to write logs. +// See createLogDirs for the full list of possible destinations. +var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") + +func createLogDirs() { + if *logDir != "" { + logDirs = append(logDirs, *logDir) + } + logDirs = append(logDirs, os.TempDir()) +} + +var ( + pid = os.Getpid() + program = filepath.Base(os.Args[0]) + host = "unknownhost" + userName = "unknownuser" +) + +func init() { + h, err := os.Hostname() + if err == nil { + host = shortHostname(h) + } + + current, err := user.Current() + if err == nil { + userName = current.Username + } + + // Sanitize userName since it may contain filepath separators on Windows. + userName = strings.Replace(userName, `\`, "_", -1) +} + +// shortHostname returns its argument, truncating at the first period. +// For instance, given "www.google.com" it returns "www". +func shortHostname(hostname string) string { + if i := strings.Index(hostname, "."); i >= 0 { + return hostname[:i] + } + return hostname +} + +// logName returns a new log file name containing tag, with start time t, and +// the name for the symlink for tag. +func logName(tag string, t time.Time) (name, link string) { + name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", + program, + host, + userName, + tag, + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + pid) + return name, program + "." + tag +} + +var onceLogDirs sync.Once + +// create creates a new log file and returns the file and its filename, which +// contains tag ("INFO", "FATAL", etc.) and t. If the file is created +// successfully, create also attempts to update the symlink for that tag, ignoring +// errors. +func create(tag string, t time.Time) (f *os.File, filename string, err error) { + onceLogDirs.Do(createLogDirs) + if len(logDirs) == 0 { + return nil, "", errors.New("log: no log dirs") + } + name, link := logName(tag, t) + var lastErr error + for _, dir := range logDirs { + fname := filepath.Join(dir, name) + f, err := os.Create(fname) + if err == nil { + symlink := filepath.Join(dir, link) + os.Remove(symlink) // ignore err + os.Symlink(name, symlink) // ignore err + return f, fname, nil + } + lastErr = err + } + return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) +} diff --git a/repo-infra/vendor/go/LICENSE b/repo-infra/vendor/go/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6a66aea5eafe0ca6a688840c47219556c552488e --- /dev/null +++ b/repo-infra/vendor/go/LICENSE @@ -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. diff --git a/repo-infra/vendor/go/path/filepath/path.go b/repo-infra/vendor/go/path/filepath/path.go new file mode 100644 index 0000000000000000000000000000000000000000..eb1cc589e9e106e03084f9f56c0e4fe2ad53df37 --- /dev/null +++ b/repo-infra/vendor/go/path/filepath/path.go @@ -0,0 +1,76 @@ +// Copyright 2009 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 filepath implements utility routines for manipulating filename paths +// in a way compatible with the target operating system-defined file paths. +// +// Functions in this package replace any occurrences of the slash ('/') character +// with os.PathSeparator when returning paths unless otherwise specified. +package sfilepath + +import ( + "os" + "path/filepath" + "sort" +) + +// Walk is filesystem.Walk that follows symlinks +func Walk(root string, walkFn filepath.WalkFunc) error { + info, err := os.Lstat(root) + if err != nil { + return walkFn(root, nil, err) + } + return walk(root, info, walkFn) +} + +func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := os.Stat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walk(filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +func readDirNames(dirname string) ([]string, error) { + f, err := os.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} diff --git a/repo-infra/verify/README.md b/repo-infra/verify/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6eca4ece1b212c802646b2f6eba218265bf04723 --- /dev/null +++ b/repo-infra/verify/README.md @@ -0,0 +1,52 @@ +# Verification scripts + +Collection of scripts that verifies that a project meets requirements set for kubernetes related projects. The scripts are to be invoked depending on the needs via CI tooling, such as Travis CI. See main Readme file on how to integrate the repo-infra in your project. + +The scripts are currently being migrated from the main kubernetes repository. If your project requires additional set of verifications, consider creating an issue/PR on repo-infra to avoid code duplication across multiple projects. + +If repo-infra is integrated at the root of your project as git submodule at path: `/repo-infra`, +then scripts can be invoked as `repo-infra/verify/verify-*.sh` + +travis.yaml example: + +``` +dist: trusty + +os: +- linux + +language: go + +go: +- 1.8 + +before_install: +- go get -u github.com/alecthomas/gometalinter + +install: +- gometalinter --install + +script: +- repo-infra/verify/verify-go-src.sh -v +- repo-infra/verify/verify-boilerplate.sh +# OR with vendoring +# - vendor/github.com/kubernetes/repo-infra/verify-go-src.sh --rootdir=$(pwd) -v +``` + +## Verify boilerplate + +Verifies that the boilerplate for various formats (go files, Makefile, etc.) is included in each file: `verify-boilerplate.sh`. + +## Verify go source code + +Runs a set of scripts on the go source code excluding vendored files: `verify-go-src.sh`. Expects `gometalinter` tooling installed (see travis file above) + +With git submodule from your repo root: `repo-infra/verify/verify-go-src.sh -v` + +With vendoring: `vendor/repo-infra/verify/verify-go-src.sh -v --rootdir $(pwd)` + +Checks include: + +1. gofmt +2. gometalinter +3. govet diff --git a/repo-infra/verify/boilerplate/BUILD b/repo-infra/verify/boilerplate/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..c405bde045daa5618f7bbc8545ad3f4984ebfcee --- /dev/null +++ b/repo-infra/verify/boilerplate/BUILD @@ -0,0 +1,3 @@ +package(default_visibility = ["//visibility:public"]) + +exports_files(glob(["*.txt"])) diff --git a/repo-infra/verify/boilerplate/boilerplate.Dockerfile.txt b/repo-infra/verify/boilerplate/boilerplate.Dockerfile.txt new file mode 100644 index 0000000000000000000000000000000000000000..384f325abf324c3a07c502ea1f07f375022425bf --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.Dockerfile.txt @@ -0,0 +1,14 @@ +# Copyright YEAR The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/repo-infra/verify/boilerplate/boilerplate.Makefile.txt b/repo-infra/verify/boilerplate/boilerplate.Makefile.txt new file mode 100644 index 0000000000000000000000000000000000000000..384f325abf324c3a07c502ea1f07f375022425bf --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.Makefile.txt @@ -0,0 +1,14 @@ +# Copyright YEAR The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/repo-infra/verify/boilerplate/boilerplate.go.txt b/repo-infra/verify/boilerplate/boilerplate.go.txt new file mode 100644 index 0000000000000000000000000000000000000000..59e740c1ee4a4c1828f4a6a6b239a9b0a6e9ffb4 --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.go.txt @@ -0,0 +1,16 @@ +/* +Copyright YEAR The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + diff --git a/repo-infra/verify/boilerplate/boilerplate.py b/repo-infra/verify/boilerplate/boilerplate.py new file mode 100755 index 0000000000000000000000000000000000000000..3507c214cff44f3b854c900d9df4d1c8c0b985b9 --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python + +# Copyright 2015 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import argparse +import difflib +import glob +import json +import mmap +import os +import re +import sys +from datetime import date + +parser = argparse.ArgumentParser() +parser.add_argument( + "filenames", + help="list of files to check, all files if unspecified", + nargs='*') + +# Rootdir defaults to the directory **above** the repo-infra dir. +rootdir = os.path.dirname(__file__) + "/../../../" +rootdir = os.path.abspath(rootdir) +parser.add_argument( + "--rootdir", default=rootdir, help="root directory to examine") + +default_boilerplate_dir = os.path.join(rootdir, "repo-infra/verify/boilerplate") +parser.add_argument( + "--boilerplate-dir", default=default_boilerplate_dir) + +parser.add_argument( + "-v", "--verbose", + help="give verbose output regarding why a file does not pass", + action="store_true") + +args = parser.parse_args() + +verbose_out = sys.stderr if args.verbose else open("/dev/null", "w") + +def get_refs(): + refs = {} + + for path in glob.glob(os.path.join(args.boilerplate_dir, "boilerplate.*.txt")): + extension = os.path.basename(path).split(".")[1] + + ref_file = open(path, 'r') + ref = ref_file.read().splitlines() + ref_file.close() + refs[extension] = ref + + return refs + +def file_passes(filename, refs, regexs): + try: + f = open(filename, 'r') + except Exception as exc: + print("Unable to open %s: %s" % (filename, exc), file=verbose_out) + return False + + data = f.read() + f.close() + + basename = os.path.basename(filename) + extension = file_extension(filename) + if extension != "": + ref = refs[extension] + else: + ref = refs[basename] + + # remove build tags from the top of Go files + if extension == "go": + p = regexs["go_build_constraints"] + (data, found) = p.subn("", data, 1) + + # remove shebang from the top of shell files + if extension == "sh" or extension == "py": + p = regexs["shebang"] + (data, found) = p.subn("", data, 1) + + data = data.splitlines() + + # if our test file is smaller than the reference it surely fails! + if len(ref) > len(data): + print('File %s smaller than reference (%d < %d)' % + (filename, len(data), len(ref)), + file=verbose_out) + return False + + # trim our file to the same number of lines as the reference file + data = data[:len(ref)] + + p = regexs["year"] + for d in data: + if p.search(d): + print('File %s is missing the year' % filename, file=verbose_out) + return False + + # Replace all occurrences of the regex "CURRENT_YEAR|...|2016|2015|2014" with "YEAR" + p = regexs["date"] + for i, d in enumerate(data): + (data[i], found) = p.subn('YEAR', d) + if found != 0: + break + + # if we don't match the reference at this point, fail + if ref != data: + print("Header in %s does not match reference, diff:" % filename, file=verbose_out) + if args.verbose: + print(file=verbose_out) + for line in difflib.unified_diff(ref, data, 'reference', filename, lineterm=''): + print(line, file=verbose_out) + print(file=verbose_out) + return False + + return True + +def file_extension(filename): + return os.path.splitext(filename)[1].split(".")[-1].lower() + +skipped_dirs = ['Godeps', 'third_party', '_gopath', '_output', '.git', + 'cluster/env.sh', 'vendor', 'test/e2e/generated/bindata.go', + 'repo-infra/verify/boilerplate/test', '.glide'] + +def normalize_files(files): + newfiles = [] + for pathname in files: + if any(x in pathname for x in skipped_dirs): + continue + newfiles.append(pathname) + for i, pathname in enumerate(newfiles): + if not os.path.isabs(pathname): + newfiles[i] = os.path.join(args.rootdir, pathname) + return newfiles + +def get_files(extensions): + files = [] + if len(args.filenames) > 0: + files = args.filenames + else: + for root, dirs, walkfiles in os.walk(args.rootdir): + # don't visit certain dirs. This is just a performance improvement + # as we would prune these later in normalize_files(). But doing it + # cuts down the amount of filesystem walking we do and cuts down + # the size of the file list + for d in skipped_dirs: + if d in dirs: + dirs.remove(d) + + for name in walkfiles: + pathname = os.path.join(root, name) + files.append(pathname) + + files = normalize_files(files) + + outfiles = [] + for pathname in files: + basename = os.path.basename(pathname) + extension = file_extension(pathname) + if extension in extensions or basename in extensions: + outfiles.append(pathname) + return outfiles + +def get_regexs(): + regexs = {} + # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing + regexs["year"] = re.compile( 'YEAR' ) + # dates can be 2014, 2015, 2016, ..., CURRENT_YEAR, company holder names can be anything + years = range(2014, date.today().year + 1) + regexs["date"] = re.compile( '(%s)' % "|".join(map(lambda l: str(l), years)) ) + # strip // +build \n\n build constraints + regexs["go_build_constraints"] = re.compile(r"^(// \+build.*\n)+\n", re.MULTILINE) + # strip #!.* from shell scripts + regexs["shebang"] = re.compile(r"^(#!.*\n)\n*", re.MULTILINE) + return regexs + +def main(): + regexs = get_regexs() + refs = get_refs() + filenames = get_files(refs.keys()) + + for filename in filenames: + if not file_passes(filename, refs, regexs): + print(filename, file=sys.stdout) + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/repo-infra/verify/boilerplate/boilerplate.py.txt b/repo-infra/verify/boilerplate/boilerplate.py.txt new file mode 100644 index 0000000000000000000000000000000000000000..384f325abf324c3a07c502ea1f07f375022425bf --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.py.txt @@ -0,0 +1,14 @@ +# Copyright YEAR The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/repo-infra/verify/boilerplate/boilerplate.sh.txt b/repo-infra/verify/boilerplate/boilerplate.sh.txt new file mode 100644 index 0000000000000000000000000000000000000000..384f325abf324c3a07c502ea1f07f375022425bf --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate.sh.txt @@ -0,0 +1,14 @@ +# Copyright YEAR The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/repo-infra/verify/boilerplate/boilerplate_test.py b/repo-infra/verify/boilerplate/boilerplate_test.py new file mode 100644 index 0000000000000000000000000000000000000000..b8d5b8e9e0f48f009a85c64ce401ccf97f5b7b49 --- /dev/null +++ b/repo-infra/verify/boilerplate/boilerplate_test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import boilerplate +import unittest +import StringIO +import os +import sys + +class TestBoilerplate(unittest.TestCase): + """ + Note: run this test from the hack/boilerplate directory. + + $ python -m unittest boilerplate_test + """ + + def test_boilerplate(self): + os.chdir("test/") + + class Args(object): + def __init__(self): + self.filenames = [] + self.rootdir = "." + self.boilerplate_dir = "../" + self.verbose = True + + # capture stdout + old_stdout = sys.stdout + sys.stdout = StringIO.StringIO() + + boilerplate.args = Args() + ret = boilerplate.main() + + output = sorted(sys.stdout.getvalue().split()) + + sys.stdout = old_stdout + + self.assertEquals( + output, ['././fail.go', '././fail.py']) diff --git a/repo-infra/verify/boilerplate/test/BUILD b/repo-infra/verify/boilerplate/test/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..15a63370473247be201b43465bbcc3a1a06daba9 --- /dev/null +++ b/repo-infra/verify/boilerplate/test/BUILD @@ -0,0 +1,17 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "fail.go", + "pass.go", + ], + tags = ["automanaged"], +) diff --git a/repo-infra/verify/boilerplate/test/fail.go b/repo-infra/verify/boilerplate/test/fail.go new file mode 100644 index 0000000000000000000000000000000000000000..16159c5ac0d9b95402819951d762d8986ddbd873 --- /dev/null +++ b/repo-infra/verify/boilerplate/test/fail.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +fail + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test diff --git a/repo-infra/verify/boilerplate/test/fail.py b/repo-infra/verify/boilerplate/test/fail.py new file mode 100644 index 0000000000000000000000000000000000000000..cbdd06ff8a1a73b42c9def4d73f6c5f3348380d9 --- /dev/null +++ b/repo-infra/verify/boilerplate/test/fail.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +# Copyright 2015 The Kubernetes Authors. +# +# failed +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/repo-infra/verify/boilerplate/test/pass.go b/repo-infra/verify/boilerplate/test/pass.go new file mode 100644 index 0000000000000000000000000000000000000000..7508448aae69ea78bcff0409b74c58c13dbb318e --- /dev/null +++ b/repo-infra/verify/boilerplate/test/pass.go @@ -0,0 +1,17 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test diff --git a/repo-infra/verify/boilerplate/test/pass.py b/repo-infra/verify/boilerplate/test/pass.py new file mode 100644 index 0000000000000000000000000000000000000000..5b7ce29a25c2a0b6b7b59f203e719bdfd1dede1a --- /dev/null +++ b/repo-infra/verify/boilerplate/test/pass.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +# Copyright 2015 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +True diff --git a/repo-infra/verify/go-tools/verify-gofmt.sh b/repo-infra/verify/go-tools/verify-gofmt.sh new file mode 100755 index 0000000000000000000000000000000000000000..1f09fd2b104a311a8cbdbc683b293272da2ced67 --- /dev/null +++ b/repo-infra/verify/go-tools/verify-gofmt.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +find_files() { + find . -not \( \ + \( \ + -wholename '*/vendor/*' \ + \) -prune \ + \) -name '*.go' +} + +GOFMT="gofmt -s" +bad_files=$(find_files | xargs $GOFMT -l) +if [[ -n "${bad_files}" ]]; then + echo "!!! '$GOFMT' needs to be run on the following files: " + echo "${bad_files}" + exit 1 +fi diff --git a/repo-infra/verify/go-tools/verify-gometalinter.sh b/repo-infra/verify/go-tools/verify-gometalinter.sh new file mode 100755 index 0000000000000000000000000000000000000000..f6024d80cbcf4dd3a0520966384c91ca974f7d7f --- /dev/null +++ b/repo-infra/verify/go-tools/verify-gometalinter.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +gometalinter --deadline=50s --vendor \ + --cyclo-over=50 --dupl-threshold=100 \ + --exclude=".*should not use dot imports \(golint\)$" \ + --disable-all \ + --enable=vet \ + --enable=deadcode \ + --enable=golint \ + --enable=vetshadow \ + --enable=gocyclo \ + --skip=.git \ + --skip=.tool \ + --skip=vendor \ + --tests \ + ./... diff --git a/repo-infra/verify/go-tools/verify-govet.sh b/repo-infra/verify/go-tools/verify-govet.sh new file mode 100755 index 0000000000000000000000000000000000000000..5f690bd31bdbc8c28c7604bcaf50d0afeb49e9f9 --- /dev/null +++ b/repo-infra/verify/go-tools/verify-govet.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +go vet -v $(go list ./... | grep -v /vendor/) diff --git a/repo-infra/verify/verify-boilerplate.sh b/repo-infra/verify/verify-boilerplate.sh new file mode 100755 index 0000000000000000000000000000000000000000..0168d336eb614bf677075618800580b868713b3d --- /dev/null +++ b/repo-infra/verify/verify-boilerplate.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Copyright 2014 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +# This script is intended to be used via subtree in a top-level directory: +# <repo>/ +# repo-infra/ +# verify/ + +REPO_ROOT=$(dirname "${BASH_SOURCE}")/../.. + +boilerDir="${REPO_ROOT}/repo-infra/verify/boilerplate" +boiler="${boilerDir}/boilerplate.py" + +files_need_boilerplate=($(${boiler} "$@")) + +# Run boilerplate.py unit tests +unitTestOut="$(mktemp)" +trap cleanup EXIT +cleanup() { + rm "${unitTestOut}" +} + +pushd "${boilerDir}" >/dev/null +if ! python -m unittest boilerplate_test 2>"${unitTestOut}"; then + echo "boilerplate_test.py failed" + echo + cat "${unitTestOut}" + exit 1 +fi +popd >/dev/null + +# Run boilerplate check +if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then + for file in "${files_need_boilerplate[@]}"; do + echo "Boilerplate header is wrong for: ${file}" + done + + exit 1 +fi diff --git a/repo-infra/verify/verify-go-src.sh b/repo-infra/verify/verify-go-src.sh new file mode 100755 index 0000000000000000000000000000000000000000..edf73dd7be6cfd41b1dc881bc6a1038a0d38be9f --- /dev/null +++ b/repo-infra/verify/verify-go-src.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +# This script is intended to be used via subtree in a top-level directory: +# <repo>/ +# repo-infra/ +# verify/ +# Or via vendoring and passing root directory as vendor/repo-infra/verify-*.sh --rootdir **full path to your repo dir** +# <repo>/ +# vendor/ +# repo-infra/ +# ... +# + + +SILENT=true +REPO_ROOT=$(dirname "${BASH_SOURCE}")/../.. + +# Convert long opts to short ones to read through getopts +for arg in "$@"; do + shift + case "$arg" in + "--rootdir") set -- "$@" "-r";; + *) + set -- "$@" "$arg" + ;; + esac +done + +OPTIND=1 +while getopts "vr:" opt; do + case ${opt} in + v) + SILENT=false + ;; + r) + REPO_ROOT=${OPTARG} + ;; + \?) + echo "Invalid flag: -${OPTARG}" >&2 + exit 1 + ;; + esac +done + +shift "$(($OPTIND-1))" + +echo "Working directory: ${REPO_ROOT}" + +GO_TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/go-tools" + +function run-cmd { + if ${SILENT}; then + "$@" &> /dev/null + else + "$@" + fi +} + +# Some useful colors. +if [[ -z "${color_start-}" ]]; then + declare -r color_start="\033[" + declare -r color_red="${color_start}0;31m" + declare -r color_yellow="${color_start}0;33m" + declare -r color_green="${color_start}0;32m" + declare -r color_norm="${color_start}0m" +fi + +function run-checks { + local -r pattern=$1 + local -r runner=$2 + + for t in $(ls ${pattern}) + do + echo -e "Verifying ${t}" + local start=$(date +%s) + cd $REPO_ROOT && run-cmd "${runner}" "${t}" && tr=$? || tr=$? + local elapsed=$(($(date +%s) - ${start})) + if [[ ${tr} -eq 0 ]]; then + echo -e "${color_green}SUCCESS${color_norm} ${t}\t${elapsed}s" + else + echo -e "${color_red}FAILED${color_norm} ${t}\t${elapsed}s" + ret=1 + fi + done +} + +if ${SILENT} ; then + echo "Running in the silent mode, run with -v if you want to see script logs." +fi + +ret=0 +run-checks "${GO_TOOLS_DIR}/*.sh" bash +exit ${ret}