diff --git a/scripts-dev/lint.sh b/scripts-dev/lint.sh
index 0647993658..f328ab57d5 100755
--- a/scripts-dev/lint.sh
+++ b/scripts-dev/lint.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
#
# Runs linting scripts over the local Synapse checkout
# isort - sorts import statements
@@ -7,15 +7,91 @@
set -e
-if [ $# -ge 1 ]
-then
- files=$*
+usage() {
+ echo
+ echo "Usage: $0 [-h] [-d] [paths...]"
+ echo
+ echo "-d"
+ echo " Lint files that have changed since the last git commit."
+ echo
+ echo " If paths are provided and this option is set, both provided paths and those"
+ echo " that have changed since the last commit will be linted."
+ echo
+ echo " If no paths are provided and this option is not set, all files will be linted."
+ echo
+ echo " Note that paths with a file extension that is not '.py' will be excluded."
+ echo "-h"
+ echo " Display this help text."
+}
+
+USING_DIFF=0
+files=()
+
+while getopts ":dh" opt; do
+ case $opt in
+ d)
+ USING_DIFF=1
+ ;;
+ h)
+ usage
+ exit
+ ;;
+ \?)
+ echo "ERROR: Invalid option: -$OPTARG" >&2
+ usage
+ exit
+ ;;
+ esac
+done
+
+# Strip any options from the command line arguments now that
+# we've finished processing them
+shift "$((OPTIND-1))"
+
+if [ $USING_DIFF -eq 1 ]; then
+ # Check both staged and non-staged changes
+ for path in $(git diff HEAD --name-only); do
+ filename=$(basename "$path")
+ file_extension="${filename##*.}"
+
+ # If an extension is present, and it's something other than 'py',
+ # then ignore this file
+ if [[ -n ${file_extension+x} && $file_extension != "py" ]]; then
+ continue
+ fi
+
+ # Append this path to our list of files to lint
+ files+=("$path")
+ done
+fi
+
+# Append any remaining arguments as files to lint
+files+=("$@")
+
+if [[ $USING_DIFF -eq 1 ]]; then
+ # If we were asked to lint changed files, and no paths were found as a result...
+ if [ ${#files[@]} -eq 0 ]; then
+ # Then print and exit
+ echo "No files found to lint."
+ exit 0
+ fi
else
- files="synapse tests scripts-dev scripts contrib synctl"
+ # If we were not asked to lint changed files, and no paths were found as a result,
+ # then lint everything!
+ if [[ -z ${files+x} ]]; then
+ # Lint all source code files and directories
+ files=("synapse" "tests" "scripts-dev" "scripts" "contrib" "synctl" "setup.py" "synmark")
+ fi
fi
-echo "Linting these locations: $files"
-isort $files
-python3 -m black $files
+echo "Linting these paths: ${files[*]}"
+echo
+
+# Print out the commands being run
+set -x
+
+isort "${files[@]}"
+python3 -m black "${files[@]}"
./scripts-dev/config-lint.sh
-flake8 $files
+flake8 "${files[@]}"
+mypy
diff --git a/scripts-dev/mypy_synapse_plugin.py b/scripts-dev/mypy_synapse_plugin.py
index a5b88731f1..5882f3a0b0 100644
--- a/scripts-dev/mypy_synapse_plugin.py
+++ b/scripts-dev/mypy_synapse_plugin.py
@@ -19,9 +19,10 @@ can crop up, e.g the cache descriptors.
from typing import Callable, Optional
+from mypy.nodes import ARG_NAMED_OPT
from mypy.plugin import MethodSigContext, Plugin
from mypy.typeops import bind_self
-from mypy.types import CallableType
+from mypy.types import CallableType, NoneType
class SynapsePlugin(Plugin):
@@ -40,8 +41,9 @@ def cached_function_method_signature(ctx: MethodSigContext) -> CallableType:
It already has *almost* the correct signature, except:
- 1. the `self` argument needs to be marked as "bound"; and
- 2. any `cache_context` argument should be removed.
+ 1. the `self` argument needs to be marked as "bound";
+ 2. any `cache_context` argument should be removed;
+ 3. an optional keyword argument `on_invalidated` should be added.
"""
# First we mark this as a bound function signature.
@@ -58,19 +60,33 @@ def cached_function_method_signature(ctx: MethodSigContext) -> CallableType:
context_arg_index = idx
break
+ arg_types = list(signature.arg_types)
+ arg_names = list(signature.arg_names)
+ arg_kinds = list(signature.arg_kinds)
+
if context_arg_index:
- arg_types = list(signature.arg_types)
arg_types.pop(context_arg_index)
-
- arg_names = list(signature.arg_names)
arg_names.pop(context_arg_index)
-
- arg_kinds = list(signature.arg_kinds)
arg_kinds.pop(context_arg_index)
- signature = signature.copy_modified(
- arg_types=arg_types, arg_names=arg_names, arg_kinds=arg_kinds,
- )
+ # Third, we add an optional "on_invalidate" argument.
+ #
+ # This is a callable which accepts no input and returns nothing.
+ calltyp = CallableType(
+ arg_types=[],
+ arg_kinds=[],
+ arg_names=[],
+ ret_type=NoneType(),
+ fallback=ctx.api.named_generic_type("builtins.function", []),
+ )
+
+ arg_types.append(calltyp)
+ arg_names.append("on_invalidate")
+ arg_kinds.append(ARG_NAMED_OPT) # Arg is an optional kwarg.
+
+ signature = signature.copy_modified(
+ arg_types=arg_types, arg_names=arg_names, arg_kinds=arg_kinds,
+ )
return signature
diff --git a/scripts-dev/sign_json b/scripts-dev/sign_json
new file mode 100755
index 0000000000..44553fb79a
--- /dev/null
+++ b/scripts-dev/sign_json
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Matrix.org Foundation C.I.C.
+#
+# 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 json
+import sys
+from json import JSONDecodeError
+
+import yaml
+from signedjson.key import read_signing_keys
+from signedjson.sign import sign_json
+
+from synapse.util import json_encoder
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="""Adds a signature to a JSON object.
+
+Example usage:
+
+ $ scripts-dev/sign_json.py -N test -k localhost.signing.key "{}"
+ {"signatures":{"test":{"ed25519:a_ZnZh":"LmPnml6iM0iR..."}}}
+""",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument(
+ "-N",
+ "--server-name",
+ help="Name to give as the local homeserver. If unspecified, will be "
+ "read from the config file.",
+ )
+
+ parser.add_argument(
+ "-k",
+ "--signing-key-path",
+ help="Path to the file containing the private ed25519 key to sign the "
+ "request with.",
+ )
+
+ parser.add_argument(
+ "-c",
+ "--config",
+ default="homeserver.yaml",
+ help=(
+ "Path to synapse config file, from which the server name and/or signing "
+ "key path will be read. Ignored if --server-name and --signing-key-path "
+ "are both given."
+ ),
+ )
+
+ input_args = parser.add_mutually_exclusive_group()
+
+ input_args.add_argument("input_data", nargs="?", help="Raw JSON to be signed.")
+
+ input_args.add_argument(
+ "-i",
+ "--input",
+ type=argparse.FileType("r"),
+ default=sys.stdin,
+ help=(
+ "A file from which to read the JSON to be signed. If neither --input nor "
+ "input_data are given, JSON will be read from stdin."
+ ),
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output",
+ type=argparse.FileType("w"),
+ default=sys.stdout,
+ help="Where to write the signed JSON. Defaults to stdout.",
+ )
+
+ args = parser.parse_args()
+
+ if not args.server_name or not args.signing_key_path:
+ read_args_from_config(args)
+
+ with open(args.signing_key_path) as f:
+ key = read_signing_keys(f)[0]
+
+ json_to_sign = args.input_data
+ if json_to_sign is None:
+ json_to_sign = args.input.read()
+
+ try:
+ obj = json.loads(json_to_sign)
+ except JSONDecodeError as e:
+ print("Unable to parse input as JSON: %s" % e, file=sys.stderr)
+ sys.exit(1)
+
+ if not isinstance(obj, dict):
+ print("Input json was not an object", file=sys.stderr)
+ sys.exit(1)
+
+ sign_json(obj, args.server_name, key)
+ for c in json_encoder.iterencode(obj):
+ args.output.write(c)
+ args.output.write("\n")
+
+
+def read_args_from_config(args: argparse.Namespace) -> None:
+ with open(args.config, "r") as fh:
+ config = yaml.safe_load(fh)
+ if not args.server_name:
+ args.server_name = config["server_name"]
+ if not args.signing_key_path:
+ args.signing_key_path = config["signing_key_path"]
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts-dev/sphinx_api_docs.sh b/scripts-dev/sphinx_api_docs.sh
deleted file mode 100644
index ee72b29657..0000000000
--- a/scripts-dev/sphinx_api_docs.sh
+++ /dev/null
@@ -1 +0,0 @@
-sphinx-apidoc -o docs/sphinx/ synapse/ -ef
|