diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ check: tests tests: # Run Rust tests if cargo is installed if command -v $(CARGO) >/dev/null 2>&1; then \ - cd $(HGROOT)/rust/hg-cpython && $(CARGO) test --quiet --all; \ + $(MAKE) rust-tests; \ fi cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) @@ -127,6 +127,13 @@ testpy-%: $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) +rust-tests: py_feature = $(shell $(PYTHON) -c \ + 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])') +rust-tests: + cd $(HGROOT)/rust/hg-cpython \ + && $(CARGO) test --quiet --all \ + --no-default-features --features "$(py_feature)" + check-code: hg manifest | xargs python contrib/check-code.py @@ -248,6 +255,7 @@ osx: .PHONY: help all local build doc cleanbutpackages clean install install-bin \ install-doc install-home install-home-bin install-home-doc \ - dist dist-notests check tests check-code format-c update-pot \ + dist dist-notests check tests rust-tests check-code format-c \ + update-pot \ $(packaging_targets) \ osx diff --git a/black.toml b/black.toml new file mode 100644 --- /dev/null +++ b/black.toml @@ -0,0 +1,17 @@ +[tool.black] +line-length = 80 +exclude = ''' +build/ +| wheelhouse/ +| dist/ +| packages/ +| \.hg/ +| \.mypy_cache/ +| \.venv/ +| mercurial/thirdparty/ +| hgext/fsmonitor/pywatchman/ +| contrib/python-zstandard/ +| contrib/grey.py +''' +skip-string-normalization = true +quiet = true diff --git a/contrib/automation/README.rst b/contrib/automation/README.rst --- a/contrib/automation/README.rst +++ b/contrib/automation/README.rst @@ -33,6 +33,46 @@ side-effects so they don't impact the lo into a remote machine, we create a temporary directory for the SSH config so the user's known hosts file isn't updated. +Try Server +========== + +There exists a *Try Server* which allows automation to run against +an arbitrary Mercurial changeset and displays results via the web. + +.. note:: + + The *Try Server* is still experimental infrastructure. + +To use the *Try Server*:: + + $ ./automation.py try + +With a custom AWS profile:: + + $ AWS_PROFILE=hg contrib/automation/automation.py try + +By default, the ``.`` revision is submitted. **Any uncommitted changes +are not submitted.** + +To switch which revision is used:: + + $ ./automation.py try -r abcdef + +Access to the *Try Server* requires access to a special AWS account. +This account is currently run by Gregory Szorc. Here is the procedure +for accessing the *Try Server*: + +1. Email Gregory Szorc at gregory.szorc@gmail.com and request a + username. This username will be stored in the public domain. +2. Wait for an email reply containing your temporary AWS credentials. +3. Log in at https://gregoryszorc-hg.signin.aws.amazon.com/console + and set a new, secure password. +4. Go to https://console.aws.amazon.com/iam/home?region=us-west-2#/security_credentials +5. Under ``Access keys for CLI, SDK, & API access``, click the + ``Create access key`` button. +6. See the ``AWS Integration`` section for instructions on + configuring your local client to use the generated credentials. + AWS Integration =============== @@ -47,12 +87,25 @@ https://boto3.amazonaws.com/v1/documenta for how ``boto3`` works. Once you have configured your environment such that ``boto3`` can find credentials, interaction with AWS should *just work*. -.. hint:: +To configure ``boto3``, you can use the ``aws configure`` command to +write out configuration files. (The ``aws`` command is typically provided +by an ``awscli`` package available in your package manager, including +``pip``.) Alternatively, you can write out files in ``~/.aws/`` directly. +e.g.:: + + # ~/.aws/config + [default] + region = us-west-2 - Typically you have a ``~/.aws/credentials`` file containing AWS - credentials. If you manage multiple credentials, you can override which - *profile* to use at run-time by setting the ``AWS_PROFILE`` environment - variable. + # ~/.aws/credentials + [default] + aws_access_key_id = XXXX + aws_secret_access_key = YYYY + +If you have multiple AWS accounts, you can name the profile something +different from ``default``. e.g. ``hg``. You can influence which profile +is used by ``boto3`` by setting the ``AWS_PROFILE`` environment variable. +e.g. ``AWS_PROFILE=hg``. Resource Management ------------------- @@ -181,3 +234,25 @@ Various dependencies to run the Mercuria Documenting them is beyond the scope of this document. Various tests also require other optional dependencies and missing dependencies will be printed by the test runner when a test is skipped. + +Releasing Windows Artifacts +=========================== + +The `automation.py` script can be used to automate the release of Windows +artifacts:: + + $ ./automation.py build-all-windows-packages --revision 5.1.1 + $ ./automation.py publish-windows-artifacts 5.1.1 + +The first command will launch an EC2 instance to build all Windows packages +and copy them into the `dist` directory relative to the repository root. The +second command will then attempt to upload these files to PyPI (via `twine`) +and to `mercurial-scm.org` (via SSH). + +Uploading to PyPI requires a PyPI account with write access to the `Mercurial` +package. You can skip PyPI uploading by passing `--no-pypi`. + +Uploading to `mercurial-scm.org` requires an SSH account on that server +with `windows` group membership and for the SSH key for that account to be the +default SSH key (e.g. `~/.ssh/id_rsa`) or in a running SSH agent. You can +skip `mercurial-scm.org` uploading by passing `--no-mercurial-scm-org`. diff --git a/contrib/automation/automation.py b/contrib/automation/automation.py --- a/contrib/automation/automation.py +++ b/contrib/automation/automation.py @@ -36,8 +36,13 @@ def bootstrap(): pip = venv_bin / 'pip' python = venv_bin / 'python' - args = [str(pip), 'install', '-r', str(REQUIREMENTS_TXT), - '--disable-pip-version-check'] + args = [ + str(pip), + 'install', + '-r', + str(REQUIREMENTS_TXT), + '--disable-pip-version-check', + ] if not venv_created: args.append('-q') @@ -45,8 +50,7 @@ def bootstrap(): subprocess.run(args, check=True) os.environ['HGAUTOMATION_BOOTSTRAPPED'] = '1' - os.environ['PATH'] = '%s%s%s' % ( - venv_bin, os.pathsep, os.environ['PATH']) + os.environ['PATH'] = '%s%s%s' % (venv_bin, os.pathsep, os.environ['PATH']) subprocess.run([str(python), __file__] + sys.argv[1:], check=True) diff --git a/contrib/automation/hgautomation/__init__.py b/contrib/automation/hgautomation/__init__.py --- a/contrib/automation/hgautomation/__init__.py +++ b/contrib/automation/hgautomation/__init__.py @@ -10,9 +10,7 @@ import pathlib import secrets -from .aws import ( - AWSConnection, -) +from .aws import AWSConnection class HGAutomation: @@ -53,7 +51,7 @@ class HGAutomation: return password - def aws_connection(self, region: str, ensure_ec2_state: bool=True): + def aws_connection(self, region: str, ensure_ec2_state: bool = True): """Obtain an AWSConnection instance bound to a specific region.""" return AWSConnection(self, region, ensure_ec2_state=ensure_ec2_state) diff --git a/contrib/automation/hgautomation/aws.py b/contrib/automation/hgautomation/aws.py --- a/contrib/automation/hgautomation/aws.py +++ b/contrib/automation/hgautomation/aws.py @@ -19,9 +19,7 @@ import time import boto3 import botocore.exceptions -from .linux import ( - BOOTSTRAP_DEBIAN, -) +from .linux import BOOTSTRAP_DEBIAN from .ssh import ( exec_command as ssh_exec_command, wait_for_ssh, @@ -32,10 +30,13 @@ from .winrm import ( ) -SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent +SOURCE_ROOT = pathlib.Path( + os.path.abspath(__file__) +).parent.parent.parent.parent -INSTALL_WINDOWS_DEPENDENCIES = (SOURCE_ROOT / 'contrib' / - 'install-windows-dependencies.ps1') +INSTALL_WINDOWS_DEPENDENCIES = ( + SOURCE_ROOT / 'contrib' / 'install-windows-dependencies.ps1' +) INSTANCE_TYPES_WITH_STORAGE = { @@ -54,6 +55,7 @@ INSTANCE_TYPES_WITH_STORAGE = { AMAZON_ACCOUNT_ID = '801119661308' DEBIAN_ACCOUNT_ID = '379101102735' +DEBIAN_ACCOUNT_ID_2 = '136693071363' UBUNTU_ACCOUNT_ID = '099720109477' @@ -106,7 +108,6 @@ SECURITY_GROUPS = { 'Description': 'RDP from entire Internet', }, ], - }, { 'FromPort': 5985, @@ -118,7 +119,7 @@ SECURITY_GROUPS = { 'Description': 'PowerShell Remoting (Windows Remote Management)', }, ], - } + }, ], }, } @@ -151,11 +152,7 @@ ASSUME_ROLE_POLICY_DOCUMENT = ''' IAM_INSTANCE_PROFILES = { - 'ephemeral-ec2-1': { - 'roles': [ - 'ephemeral-ec2-role-1', - ], - } + 'ephemeral-ec2-1': {'roles': ['ephemeral-ec2-role-1',],} } @@ -225,7 +222,7 @@ Install-WindowsFeature -Name Net-Framewo class AWSConnection: """Manages the state of a connection with AWS.""" - def __init__(self, automation, region: str, ensure_ec2_state: bool=True): + def __init__(self, automation, region: str, ensure_ec2_state: bool = True): self.automation = automation self.local_state_path = automation.state_path @@ -256,10 +253,19 @@ def rsa_key_fingerprint(p: pathlib.Path) # TODO use rsa package. res = subprocess.run( - ['openssl', 'pkcs8', '-in', str(p), '-nocrypt', '-topk8', - '-outform', 'DER'], + [ + 'openssl', + 'pkcs8', + '-in', + str(p), + '-nocrypt', + '-topk8', + '-outform', + 'DER', + ], capture_output=True, - check=True) + check=True, + ) sha1 = hashlib.sha1(res.stdout).hexdigest() return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2])) @@ -270,7 +276,7 @@ def ensure_key_pairs(state_path: pathlib for kpi in ec2resource.key_pairs.all(): if kpi.name.startswith(prefix): - remote_existing[kpi.name[len(prefix):]] = kpi.key_fingerprint + remote_existing[kpi.name[len(prefix) :]] = kpi.key_fingerprint # Validate that we have these keys locally. key_path = state_path / 'keys' @@ -296,7 +302,7 @@ def ensure_key_pairs(state_path: pathlib if not f.startswith('keypair-') or not f.endswith('.pub'): continue - name = f[len('keypair-'):-len('.pub')] + name = f[len('keypair-') : -len('.pub')] pub_full = key_path / f priv_full = key_path / ('keypair-%s' % name) @@ -305,8 +311,9 @@ def ensure_key_pairs(state_path: pathlib data = fh.read() if not data.startswith('ssh-rsa '): - print('unexpected format for key pair file: %s; removing' % - pub_full) + print( + 'unexpected format for key pair file: %s; removing' % pub_full + ) pub_full.unlink() priv_full.unlink() continue @@ -326,8 +333,10 @@ def ensure_key_pairs(state_path: pathlib del local_existing[name] elif remote_existing[name] != local_existing[name]: - print('key fingerprint mismatch for %s; ' - 'removing from local and remote' % name) + print( + 'key fingerprint mismatch for %s; ' + 'removing from local and remote' % name + ) remove_local(name) remove_remote('%s%s' % (prefix, name)) del local_existing[name] @@ -355,15 +364,18 @@ def ensure_key_pairs(state_path: pathlib subprocess.run( ['ssh-keygen', '-y', '-f', str(priv_full)], stdout=fh, - check=True) + check=True, + ) pub_full.chmod(0o0600) def delete_instance_profile(profile): for role in profile.roles: - print('removing role %s from instance profile %s' % (role.name, - profile.name)) + print( + 'removing role %s from instance profile %s' + % (role.name, profile.name) + ) profile.remove_role(RoleName=role.name) print('deleting instance profile %s' % profile.name) @@ -377,7 +389,7 @@ def ensure_iam_state(iamclient, iamresou for profile in iamresource.instance_profiles.all(): if profile.name.startswith(prefix): - remote_profiles[profile.name[len(prefix):]] = profile + remote_profiles[profile.name[len(prefix) :]] = profile for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)): delete_instance_profile(remote_profiles[name]) @@ -387,7 +399,7 @@ def ensure_iam_state(iamclient, iamresou for role in iamresource.roles.all(): if role.name.startswith(prefix): - remote_roles[role.name[len(prefix):]] = role + remote_roles[role.name[len(prefix) :]] = role for name in sorted(set(remote_roles) - set(IAM_ROLES)): role = remote_roles[name] @@ -403,7 +415,8 @@ def ensure_iam_state(iamclient, iamresou print('creating IAM instance profile %s' % actual) profile = iamresource.create_instance_profile( - InstanceProfileName=actual) + InstanceProfileName=actual + ) remote_profiles[name] = profile waiter = iamclient.get_waiter('instance_profile_exists') @@ -452,23 +465,12 @@ def find_image(ec2resource, owner_id, na images = ec2resource.images.filter( Filters=[ - { - 'Name': 'owner-id', - 'Values': [owner_id], - }, - { - 'Name': 'state', - 'Values': ['available'], - }, - { - 'Name': 'image-type', - 'Values': ['machine'], - }, - { - 'Name': 'name', - 'Values': [name], - }, - ]) + {'Name': 'owner-id', 'Values': [owner_id],}, + {'Name': 'state', 'Values': ['available'],}, + {'Name': 'image-type', 'Values': ['machine'],}, + {'Name': 'name', 'Values': [name],}, + ] + ) for image in images: return image @@ -486,7 +488,7 @@ def ensure_security_groups(ec2resource, for group in ec2resource.security_groups.all(): if group.group_name.startswith(prefix): - existing[group.group_name[len(prefix):]] = group + existing[group.group_name[len(prefix) :]] = group purge = set(existing) - set(SECURITY_GROUPS) @@ -506,13 +508,10 @@ def ensure_security_groups(ec2resource, print('adding security group %s' % actual) group_res = ec2resource.create_security_group( - Description=group['description'], - GroupName=actual, + Description=group['description'], GroupName=actual, ) - group_res.authorize_ingress( - IpPermissions=group['ingress'], - ) + group_res.authorize_ingress(IpPermissions=group['ingress'],) security_groups[name] = group_res @@ -576,8 +575,10 @@ def wait_for_ip_addresses(instances): instance.reload() continue - print('public IP address for %s: %s' % ( - instance.id, instance.public_ip_address)) + print( + 'public IP address for %s: %s' + % (instance.id, instance.public_ip_address) + ) break @@ -602,10 +603,7 @@ def wait_for_ssm(ssmclient, instances): while True: res = ssmclient.describe_instance_information( Filters=[ - { - 'Key': 'InstanceIds', - 'Values': [i.id for i in instances], - }, + {'Key': 'InstanceIds', 'Values': [i.id for i in instances],}, ], ) @@ -627,9 +625,7 @@ def run_ssm_command(ssmclient, instances InstanceIds=[i.id for i in instances], DocumentName=document_name, Parameters=parameters, - CloudWatchOutputConfig={ - 'CloudWatchOutputEnabled': True, - }, + CloudWatchOutputConfig={'CloudWatchOutputEnabled': True,}, ) command_id = res['Command']['CommandId'] @@ -638,8 +634,7 @@ def run_ssm_command(ssmclient, instances while True: try: res = ssmclient.get_command_invocation( - CommandId=command_id, - InstanceId=instance.id, + CommandId=command_id, InstanceId=instance.id, ) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'InvocationDoesNotExist': @@ -654,8 +649,9 @@ def run_ssm_command(ssmclient, instances elif res['Status'] in ('Pending', 'InProgress', 'Delayed'): time.sleep(2) else: - raise Exception('command failed on %s: %s' % ( - instance.id, res['Status'])) + raise Exception( + 'command failed on %s: %s' % (instance.id, res['Status']) + ) @contextlib.contextmanager @@ -691,7 +687,9 @@ def temporary_ec2_instances(ec2resource, @contextlib.contextmanager -def create_temp_windows_ec2_instances(c: AWSConnection, config): +def create_temp_windows_ec2_instances( + c: AWSConnection, config, bootstrap: bool = False +): """Create temporary Windows EC2 instances. This is a higher-level wrapper around ``create_temp_ec2_instances()`` that @@ -710,11 +708,15 @@ def create_temp_windows_ec2_instances(c: config['IamInstanceProfile'] = { 'Name': 'hg-ephemeral-ec2-1', } - config.setdefault('TagSpecifications', []).append({ - 'ResourceType': 'instance', - 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}], - }) - config['UserData'] = WINDOWS_USER_DATA % password + config.setdefault('TagSpecifications', []).append( + { + 'ResourceType': 'instance', + 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}], + } + ) + + if bootstrap: + config['UserData'] = WINDOWS_USER_DATA % password with temporary_ec2_instances(c.ec2resource, config) as instances: wait_for_ip_addresses(instances) @@ -722,7 +724,9 @@ def create_temp_windows_ec2_instances(c: print('waiting for Windows Remote Management service...') for instance in instances: - client = wait_for_winrm(instance.public_ip_address, 'Administrator', password) + client = wait_for_winrm( + instance.public_ip_address, 'Administrator', password + ) print('established WinRM connection to %s' % instance.id) instance.winrm_client = client @@ -747,14 +751,17 @@ def find_and_reconcile_image(ec2resource # Store a reference to a good image so it can be returned one the # image state is reconciled. images = ec2resource.images.filter( - Filters=[{'Name': 'name', 'Values': [name]}]) + Filters=[{'Name': 'name', 'Values': [name]}] + ) existing_image = None for image in images: if image.tags is None: - print('image %s for %s lacks required tags; removing' % ( - image.id, image.name)) + print( + 'image %s for %s lacks required tags; removing' + % (image.id, image.name) + ) remove_ami(ec2resource, image) else: tags = {t['Key']: t['Value'] for t in image.tags} @@ -762,15 +769,18 @@ def find_and_reconcile_image(ec2resource if tags.get('HGIMAGEFINGERPRINT') == fingerprint: existing_image = image else: - print('image %s for %s has wrong fingerprint; removing' % ( - image.id, image.name)) + print( + 'image %s for %s has wrong fingerprint; removing' + % (image.id, image.name) + ) remove_ami(ec2resource, image) return existing_image -def create_ami_from_instance(ec2client, instance, name, description, - fingerprint): +def create_ami_from_instance( + ec2client, instance, name, description, fingerprint +): """Create an AMI from a running instance. Returns the ``ec2resource.Image`` representing the created AMI. @@ -778,36 +788,26 @@ def create_ami_from_instance(ec2client, instance.stop() ec2client.get_waiter('instance_stopped').wait( - InstanceIds=[instance.id], - WaiterConfig={ - 'Delay': 5, - }) + InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} + ) print('%s is stopped' % instance.id) - image = instance.create_image( - Name=name, - Description=description, - ) + image = instance.create_image(Name=name, Description=description,) - image.create_tags(Tags=[ - { - 'Key': 'HGIMAGEFINGERPRINT', - 'Value': fingerprint, - }, - ]) + image.create_tags( + Tags=[{'Key': 'HGIMAGEFINGERPRINT', 'Value': fingerprint,},] + ) print('waiting for image %s' % image.id) - ec2client.get_waiter('image_available').wait( - ImageIds=[image.id], - ) + ec2client.get_waiter('image_available').wait(ImageIds=[image.id],) print('image %s available as %s' % (image.id, image.name)) return image -def ensure_linux_dev_ami(c: AWSConnection, distro='debian9', prefix='hg-'): +def ensure_linux_dev_ami(c: AWSConnection, distro='debian10', prefix='hg-'): """Ensures a Linux development AMI is available and up-to-date. Returns an ``ec2.Image`` of either an existing AMI or a newly-built one. @@ -821,28 +821,26 @@ def ensure_linux_dev_ami(c: AWSConnectio image = find_image( ec2resource, DEBIAN_ACCOUNT_ID, - 'debian-stretch-hvm-x86_64-gp2-2019-02-19-26620', + 'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994', + ) + ssh_username = 'admin' + elif distro == 'debian10': + image = find_image( + ec2resource, DEBIAN_ACCOUNT_ID_2, 'debian-10-amd64-20190909-10', ) ssh_username = 'admin' elif distro == 'ubuntu18.04': image = find_image( ec2resource, UBUNTU_ACCOUNT_ID, - 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190403', - ) - ssh_username = 'ubuntu' - elif distro == 'ubuntu18.10': - image = find_image( - ec2resource, - UBUNTU_ACCOUNT_ID, - 'ubuntu/images/hvm-ssd/ubuntu-cosmic-18.10-amd64-server-20190402', + 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190918', ) ssh_username = 'ubuntu' elif distro == 'ubuntu19.04': image = find_image( ec2resource, UBUNTU_ACCOUNT_ID, - 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190417', + 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190918', ) ssh_username = 'ubuntu' else: @@ -854,7 +852,7 @@ def ensure_linux_dev_ami(c: AWSConnectio 'DeviceName': image.block_device_mappings[0]['DeviceName'], 'Ebs': { 'DeleteOnTermination': True, - 'VolumeSize': 8, + 'VolumeSize': 10, 'VolumeType': 'gp2', }, }, @@ -870,10 +868,12 @@ def ensure_linux_dev_ami(c: AWSConnectio 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id], } - requirements2_path = (pathlib.Path(__file__).parent.parent / - 'linux-requirements-py2.txt') - requirements3_path = (pathlib.Path(__file__).parent.parent / - 'linux-requirements-py3.txt') + requirements2_path = ( + pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt' + ) + requirements3_path = ( + pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt' + ) with requirements2_path.open('r', encoding='utf-8') as fh: requirements2 = fh.read() with requirements3_path.open('r', encoding='utf-8') as fh: @@ -881,12 +881,14 @@ def ensure_linux_dev_ami(c: AWSConnectio # Compute a deterministic fingerprint to determine whether image needs to # be regenerated. - fingerprint = resolve_fingerprint({ - 'instance_config': config, - 'bootstrap_script': BOOTSTRAP_DEBIAN, - 'requirements_py2': requirements2, - 'requirements_py3': requirements3, - }) + fingerprint = resolve_fingerprint( + { + 'instance_config': config, + 'bootstrap_script': BOOTSTRAP_DEBIAN, + 'requirements_py2': requirements2, + 'requirements_py3': requirements3, + } + ) existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) @@ -901,9 +903,11 @@ def ensure_linux_dev_ami(c: AWSConnectio instance = instances[0] client = wait_for_ssh( - instance.public_ip_address, 22, + instance.public_ip_address, + 22, username=ssh_username, - key_filename=str(c.key_pair_path_private('automation'))) + key_filename=str(c.key_pair_path_private('automation')), + ) home = '/home/%s' % ssh_username @@ -925,8 +929,9 @@ def ensure_linux_dev_ami(c: AWSConnectio fh.chmod(0o0700) print('executing bootstrap') - chan, stdin, stdout = ssh_exec_command(client, - '%s/bootstrap' % home) + chan, stdin, stdout = ssh_exec_command( + client, '%s/bootstrap' % home + ) stdin.close() for line in stdout: @@ -936,17 +941,28 @@ def ensure_linux_dev_ami(c: AWSConnectio if res: raise Exception('non-0 exit from bootstrap: %d' % res) - print('bootstrap completed; stopping %s to create %s' % ( - instance.id, name)) + print( + 'bootstrap completed; stopping %s to create %s' + % (instance.id, name) + ) - return create_ami_from_instance(ec2client, instance, name, - 'Mercurial Linux development environment', - fingerprint) + return create_ami_from_instance( + ec2client, + instance, + name, + 'Mercurial Linux development environment', + fingerprint, + ) @contextlib.contextmanager -def temporary_linux_dev_instances(c: AWSConnection, image, instance_type, - prefix='hg-', ensure_extra_volume=False): +def temporary_linux_dev_instances( + c: AWSConnection, + image, + instance_type, + prefix='hg-', + ensure_extra_volume=False, +): """Create temporary Linux development EC2 instances. Context manager resolves to a list of ``ec2.Instance`` that were created @@ -970,7 +986,7 @@ def temporary_linux_dev_instances(c: AWS 'DeviceName': image.block_device_mappings[0]['DeviceName'], 'Ebs': { 'DeleteOnTermination': True, - 'VolumeSize': 8, + 'VolumeSize': 12, 'VolumeType': 'gp2', }, } @@ -978,8 +994,9 @@ def temporary_linux_dev_instances(c: AWS # This is not an exhaustive list of instance types having instance storage. # But - if (ensure_extra_volume - and not instance_type.startswith(tuple(INSTANCE_TYPES_WITH_STORAGE))): + if ensure_extra_volume and not instance_type.startswith( + tuple(INSTANCE_TYPES_WITH_STORAGE) + ): main_device = block_device_mappings[0]['DeviceName'] if main_device == 'xvda': @@ -987,17 +1004,20 @@ def temporary_linux_dev_instances(c: AWS elif main_device == '/dev/sda1': second_device = '/dev/sdb' else: - raise ValueError('unhandled primary EBS device name: %s' % - main_device) + raise ValueError( + 'unhandled primary EBS device name: %s' % main_device + ) - block_device_mappings.append({ - 'DeviceName': second_device, - 'Ebs': { - 'DeleteOnTermination': True, - 'VolumeSize': 8, - 'VolumeType': 'gp2', + block_device_mappings.append( + { + 'DeviceName': second_device, + 'Ebs': { + 'DeleteOnTermination': True, + 'VolumeSize': 8, + 'VolumeType': 'gp2', + }, } - }) + ) config = { 'BlockDeviceMappings': block_device_mappings, @@ -1018,9 +1038,11 @@ def temporary_linux_dev_instances(c: AWS for instance in instances: client = wait_for_ssh( - instance.public_ip_address, 22, + instance.public_ip_address, + 22, username='hg', - key_filename=ssh_private_key_path) + key_filename=ssh_private_key_path, + ) instance.ssh_client = client instance.ssh_private_key_path = ssh_private_key_path @@ -1032,8 +1054,9 @@ def temporary_linux_dev_instances(c: AWS instance.ssh_client.close() -def ensure_windows_dev_ami(c: AWSConnection, prefix='hg-', - base_image_name=WINDOWS_BASE_IMAGE_NAME): +def ensure_windows_dev_ami( + c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME +): """Ensure Windows Development AMI is available and up-to-date. If necessary, a modern AMI will be built by starting a temporary EC2 @@ -1092,6 +1115,23 @@ def ensure_windows_dev_ami(c: AWSConnect with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh: commands.extend(l.rstrip() for l in fh) + # Schedule run of EC2Launch on next boot. This ensures that UserData + # is executed. + # We disable setComputerName because it forces a reboot. + # We set an explicit admin password because this causes UserData to run + # as Administrator instead of System. + commands.extend( + [ + r'''Set-Content -Path C:\ProgramData\Amazon\EC2-Windows\Launch\Config\LaunchConfig.json ''' + r'''-Value '{"setComputerName": false, "setWallpaper": true, "addDnsSuffixList": true, ''' + r'''"extendBootVolumeSize": true, "handleUserData": true, ''' + r'''"adminPasswordType": "Specify", "adminPassword": "%s"}' ''' + % c.automation.default_password(), + r'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 ' + r'–Schedule', + ] + ) + # Disable Windows Defender when bootstrapping because it just slows # things down. commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true') @@ -1099,13 +1139,15 @@ def ensure_windows_dev_ami(c: AWSConnect # Compute a deterministic fingerprint to determine whether image needs # to be regenerated. - fingerprint = resolve_fingerprint({ - 'instance_config': config, - 'user_data': WINDOWS_USER_DATA, - 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL, - 'bootstrap_commands': commands, - 'base_image_name': base_image_name, - }) + fingerprint = resolve_fingerprint( + { + 'instance_config': config, + 'user_data': WINDOWS_USER_DATA, + 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL, + 'bootstrap_commands': commands, + 'base_image_name': base_image_name, + } + ) existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) @@ -1114,7 +1156,9 @@ def ensure_windows_dev_ami(c: AWSConnect print('no suitable Windows development image found; creating one...') - with create_temp_windows_ec2_instances(c, config) as instances: + with create_temp_windows_ec2_instances( + c, config, bootstrap=True + ) as instances: assert len(instances) == 1 instance = instances[0] @@ -1130,9 +1174,7 @@ def ensure_windows_dev_ami(c: AWSConnect ssmclient, [instance], 'AWS-RunPowerShellScript', - { - 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'), - }, + {'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),}, ) # Reboot so all updates are fully applied. @@ -1144,10 +1186,8 @@ def ensure_windows_dev_ami(c: AWSConnect print('rebooting instance %s' % instance.id) instance.stop() ec2client.get_waiter('instance_stopped').wait( - InstanceIds=[instance.id], - WaiterConfig={ - 'Delay': 5, - }) + InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} + ) instance.start() wait_for_ip_addresses([instance]) @@ -1158,8 +1198,11 @@ def ensure_windows_dev_ami(c: AWSConnect # TODO figure out a workaround. print('waiting for Windows Remote Management to come back...') - client = wait_for_winrm(instance.public_ip_address, 'Administrator', - c.automation.default_password()) + client = wait_for_winrm( + instance.public_ip_address, + 'Administrator', + c.automation.default_password(), + ) print('established WinRM connection to %s' % instance.id) instance.winrm_client = client @@ -1167,14 +1210,23 @@ def ensure_windows_dev_ami(c: AWSConnect run_powershell(instance.winrm_client, '\n'.join(commands)) print('bootstrap completed; stopping %s to create image' % instance.id) - return create_ami_from_instance(ec2client, instance, name, - 'Mercurial Windows development environment', - fingerprint) + return create_ami_from_instance( + ec2client, + instance, + name, + 'Mercurial Windows development environment', + fingerprint, + ) @contextlib.contextmanager -def temporary_windows_dev_instances(c: AWSConnection, image, instance_type, - prefix='hg-', disable_antivirus=False): +def temporary_windows_dev_instances( + c: AWSConnection, + image, + instance_type, + prefix='hg-', + disable_antivirus=False, +): """Create a temporary Windows development EC2 instance. Context manager resolves to the list of ``EC2.Instance`` that were created. @@ -1204,6 +1256,7 @@ def temporary_windows_dev_instances(c: A for instance in instances: run_powershell( instance.winrm_client, - 'Set-MpPreference -DisableRealtimeMonitoring $true') + 'Set-MpPreference -DisableRealtimeMonitoring $true', + ) yield instances diff --git a/contrib/automation/hgautomation/cli.py b/contrib/automation/hgautomation/cli.py --- a/contrib/automation/hgautomation/cli.py +++ b/contrib/automation/hgautomation/cli.py @@ -17,16 +17,20 @@ from . import ( aws, HGAutomation, linux, + try_server, windows, ) -SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent +SOURCE_ROOT = pathlib.Path( + os.path.abspath(__file__) +).parent.parent.parent.parent DIST_PATH = SOURCE_ROOT / 'dist' -def bootstrap_linux_dev(hga: HGAutomation, aws_region, distros=None, - parallel=False): +def bootstrap_linux_dev( + hga: HGAutomation, aws_region, distros=None, parallel=False +): c = hga.aws_connection(aws_region) if distros: @@ -58,8 +62,9 @@ def bootstrap_windows_dev(hga: HGAutomat print('Windows development AMI available as %s' % image.id) -def build_inno(hga: HGAutomation, aws_region, arch, revision, version, - base_image_name): +def build_inno( + hga: HGAutomation, aws_region, arch, revision, version, base_image_name +): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) DIST_PATH.mkdir(exist_ok=True) @@ -70,13 +75,14 @@ def build_inno(hga: HGAutomation, aws_re windows.synchronize_hg(SOURCE_ROOT, revision, instance) for a in arch: - windows.build_inno_installer(instance.winrm_client, a, - DIST_PATH, - version=version) + windows.build_inno_installer( + instance.winrm_client, a, DIST_PATH, version=version + ) -def build_wix(hga: HGAutomation, aws_region, arch, revision, version, - base_image_name): +def build_wix( + hga: HGAutomation, aws_region, arch, revision, version, base_image_name +): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) DIST_PATH.mkdir(exist_ok=True) @@ -87,12 +93,14 @@ def build_wix(hga: HGAutomation, aws_reg windows.synchronize_hg(SOURCE_ROOT, revision, instance) for a in arch: - windows.build_wix_installer(instance.winrm_client, a, - DIST_PATH, version=version) + windows.build_wix_installer( + instance.winrm_client, a, DIST_PATH, version=version + ) -def build_windows_wheel(hga: HGAutomation, aws_region, arch, revision, - base_image_name): +def build_windows_wheel( + hga: HGAutomation, aws_region, arch, revision, base_image_name +): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) DIST_PATH.mkdir(exist_ok=True) @@ -106,8 +114,9 @@ def build_windows_wheel(hga: HGAutomatio windows.build_wheel(instance.winrm_client, a, DIST_PATH) -def build_all_windows_packages(hga: HGAutomation, aws_region, revision, - version, base_image_name): +def build_all_windows_packages( + hga: HGAutomation, aws_region, revision, version, base_image_name +): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) DIST_PATH.mkdir(exist_ok=True) @@ -123,11 +132,13 @@ def build_all_windows_packages(hga: HGAu windows.purge_hg(winrm_client) windows.build_wheel(winrm_client, arch, DIST_PATH) windows.purge_hg(winrm_client) - windows.build_inno_installer(winrm_client, arch, DIST_PATH, - version=version) + windows.build_inno_installer( + winrm_client, arch, DIST_PATH, version=version + ) windows.purge_hg(winrm_client) - windows.build_wix_installer(winrm_client, arch, DIST_PATH, - version=version) + windows.build_wix_installer( + winrm_client, arch, DIST_PATH, version=version + ) def terminate_ec2_instances(hga: HGAutomation, aws_region): @@ -140,8 +151,15 @@ def purge_ec2_resources(hga: HGAutomatio aws.remove_resources(c) -def run_tests_linux(hga: HGAutomation, aws_region, instance_type, - python_version, test_flags, distro, filesystem): +def run_tests_linux( + hga: HGAutomation, + aws_region, + instance_type, + python_version, + test_flags, + distro, + filesystem, +): c = hga.aws_connection(aws_region) image = aws.ensure_linux_dev_ami(c, distro=distro) @@ -150,17 +168,17 @@ def run_tests_linux(hga: HGAutomation, a ensure_extra_volume = filesystem not in ('default', 'tmpfs') with aws.temporary_linux_dev_instances( - c, image, instance_type, - ensure_extra_volume=ensure_extra_volume) as insts: + c, image, instance_type, ensure_extra_volume=ensure_extra_volume + ) as insts: instance = insts[0] - linux.prepare_exec_environment(instance.ssh_client, - filesystem=filesystem) + linux.prepare_exec_environment( + instance.ssh_client, filesystem=filesystem + ) linux.synchronize_hg(SOURCE_ROOT, instance, '.') t_prepared = time.time() - linux.run_tests(instance.ssh_client, python_version, - test_flags) + linux.run_tests(instance.ssh_client, python_version, test_flags) t_done = time.time() t_setup = t_prepared - t_start @@ -168,21 +186,53 @@ def run_tests_linux(hga: HGAutomation, a print( 'total time: %.1fs; setup: %.1fs; tests: %.1fs; setup overhead: %.1f%%' - % (t_all, t_setup, t_done - t_prepared, t_setup / t_all * 100.0)) + % (t_all, t_setup, t_done - t_prepared, t_setup / t_all * 100.0) + ) -def run_tests_windows(hga: HGAutomation, aws_region, instance_type, - python_version, arch, test_flags, base_image_name): +def run_tests_windows( + hga: HGAutomation, + aws_region, + instance_type, + python_version, + arch, + test_flags, + base_image_name, +): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) - with aws.temporary_windows_dev_instances(c, image, instance_type, - disable_antivirus=True) as insts: + with aws.temporary_windows_dev_instances( + c, image, instance_type, disable_antivirus=True + ) as insts: instance = insts[0] windows.synchronize_hg(SOURCE_ROOT, '.', instance) - windows.run_tests(instance.winrm_client, python_version, arch, - test_flags) + windows.run_tests( + instance.winrm_client, python_version, arch, test_flags + ) + + +def publish_windows_artifacts( + hg: HGAutomation, + aws_region, + version: str, + pypi: bool, + mercurial_scm_org: bool, + ssh_username: str, +): + windows.publish_artifacts( + DIST_PATH, + version, + pypi=pypi, + mercurial_scm_org=mercurial_scm_org, + ssh_username=ssh_username, + ) + + +def run_try(hga: HGAutomation, aws_region: str, rev: str): + c = hga.aws_connection(aws_region, ensure_ec2_state=False) + try_server.trigger_try(c, rev=rev) def get_parser(): @@ -194,25 +244,21 @@ def get_parser(): help='Path for local state files', ) parser.add_argument( - '--aws-region', - help='AWS region to use', - default='us-west-1', + '--aws-region', help='AWS region to use', default='us-west-2', ) subparsers = parser.add_subparsers() sp = subparsers.add_parser( - 'bootstrap-linux-dev', - help='Bootstrap Linux development environments', + 'bootstrap-linux-dev', help='Bootstrap Linux development environments', ) sp.add_argument( - '--distros', - help='Comma delimited list of distros to bootstrap', + '--distros', help='Comma delimited list of distros to bootstrap', ) sp.add_argument( '--parallel', action='store_true', - help='Generate AMIs in parallel (not CTRL-c safe)' + help='Generate AMIs in parallel (not CTRL-c safe)', ) sp.set_defaults(func=bootstrap_linux_dev) @@ -228,17 +274,13 @@ def get_parser(): sp.set_defaults(func=bootstrap_windows_dev) sp = subparsers.add_parser( - 'build-all-windows-packages', - help='Build all Windows packages', + 'build-all-windows-packages', help='Build all Windows packages', ) sp.add_argument( - '--revision', - help='Mercurial revision to build', - default='.', + '--revision', help='Mercurial revision to build', default='.', ) sp.add_argument( - '--version', - help='Mercurial version string to use', + '--version', help='Mercurial version string to use', ) sp.add_argument( '--base-image-name', @@ -248,8 +290,7 @@ def get_parser(): sp.set_defaults(func=build_all_windows_packages) sp = subparsers.add_parser( - 'build-inno', - help='Build Inno Setup installer(s)', + 'build-inno', help='Build Inno Setup installer(s)', ) sp.add_argument( '--arch', @@ -259,13 +300,10 @@ def get_parser(): default=['x64'], ) sp.add_argument( - '--revision', - help='Mercurial revision to build', - default='.', + '--revision', help='Mercurial revision to build', default='.', ) sp.add_argument( - '--version', - help='Mercurial version string to use in installer', + '--version', help='Mercurial version string to use in installer', ) sp.add_argument( '--base-image-name', @@ -275,8 +313,7 @@ def get_parser(): sp.set_defaults(func=build_inno) sp = subparsers.add_parser( - 'build-windows-wheel', - help='Build Windows wheel(s)', + 'build-windows-wheel', help='Build Windows wheel(s)', ) sp.add_argument( '--arch', @@ -286,9 +323,7 @@ def get_parser(): default=['x64'], ) sp.add_argument( - '--revision', - help='Mercurial revision to build', - default='.', + '--revision', help='Mercurial revision to build', default='.', ) sp.add_argument( '--base-image-name', @@ -297,10 +332,7 @@ def get_parser(): ) sp.set_defaults(func=build_windows_wheel) - sp = subparsers.add_parser( - 'build-wix', - help='Build WiX installer(s)' - ) + sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)') sp.add_argument( '--arch', help='Architecture to build for', @@ -309,13 +341,10 @@ def get_parser(): default=['x64'], ) sp.add_argument( - '--revision', - help='Mercurial revision to build', - default='.', + '--revision', help='Mercurial revision to build', default='.', ) sp.add_argument( - '--version', - help='Mercurial version string to use in installer', + '--version', help='Mercurial version string to use in installer', ) sp.add_argument( '--base-image-name', @@ -331,20 +360,16 @@ def get_parser(): sp.set_defaults(func=terminate_ec2_instances) sp = subparsers.add_parser( - 'purge-ec2-resources', - help='Purge all EC2 resources managed by us', + 'purge-ec2-resources', help='Purge all EC2 resources managed by us', ) sp.set_defaults(func=purge_ec2_resources) - sp = subparsers.add_parser( - 'run-tests-linux', - help='Run tests on Linux', - ) + sp = subparsers.add_parser('run-tests-linux', help='Run tests on Linux',) sp.add_argument( '--distro', help='Linux distribution to run tests on', choices=linux.DISTROS, - default='debian9', + default='debian10', ) sp.add_argument( '--filesystem', @@ -360,8 +385,18 @@ def get_parser(): sp.add_argument( '--python-version', help='Python version to use', - choices={'system2', 'system3', '2.7', '3.5', '3.6', '3.7', '3.8', - 'pypy', 'pypy3.5', 'pypy3.6'}, + choices={ + 'system2', + 'system3', + '2.7', + '3.5', + '3.6', + '3.7', + '3.8', + 'pypy', + 'pypy3.5', + 'pypy3.6', + }, default='system2', ) sp.add_argument( @@ -372,13 +407,10 @@ def get_parser(): sp.set_defaults(func=run_tests_linux) sp = subparsers.add_parser( - 'run-tests-windows', - help='Run tests on Windows', + 'run-tests-windows', help='Run tests on Windows', ) sp.add_argument( - '--instance-type', - help='EC2 instance type to use', - default='t3.medium', + '--instance-type', help='EC2 instance type to use', default='t3.medium', ) sp.add_argument( '--python-version', @@ -393,8 +425,7 @@ def get_parser(): default='x64', ) sp.add_argument( - '--test-flags', - help='Extra command line flags to pass to run-tests.py', + '--test-flags', help='Extra command line flags to pass to run-tests.py', ) sp.add_argument( '--base-image-name', @@ -403,6 +434,38 @@ def get_parser(): ) sp.set_defaults(func=run_tests_windows) + sp = subparsers.add_parser( + 'publish-windows-artifacts', + help='Publish built Windows artifacts (wheels, installers, etc)', + ) + sp.add_argument( + '--no-pypi', + dest='pypi', + action='store_false', + default=True, + help='Skip uploading to PyPI', + ) + sp.add_argument( + '--no-mercurial-scm-org', + dest='mercurial_scm_org', + action='store_false', + default=True, + help='Skip uploading to www.mercurial-scm.org', + ) + sp.add_argument( + '--ssh-username', help='SSH username for mercurial-scm.org', + ) + sp.add_argument( + 'version', help='Mercurial version string to locate local packages', + ) + sp.set_defaults(func=publish_windows_artifacts) + + sp = subparsers.add_parser( + 'try', help='Run CI automation against a custom changeset' + ) + sp.add_argument('-r', '--rev', default='.', help='Revision to run CI on') + sp.set_defaults(func=run_try) + return parser diff --git a/contrib/automation/hgautomation/linux.py b/contrib/automation/hgautomation/linux.py --- a/contrib/automation/hgautomation/linux.py +++ b/contrib/automation/hgautomation/linux.py @@ -13,39 +13,37 @@ import shlex import subprocess import tempfile -from .ssh import ( - exec_command, -) +from .ssh import exec_command # Linux distributions that are supported. DISTROS = { 'debian9', + 'debian10', 'ubuntu18.04', - 'ubuntu18.10', 'ubuntu19.04', } INSTALL_PYTHONS = r''' PYENV2_VERSIONS="2.7.16 pypy2.7-7.1.1" -PYENV3_VERSIONS="3.5.7 3.6.8 3.7.3 3.8-dev pypy3.5-7.0.0 pypy3.6-7.1.1" +PYENV3_VERSIONS="3.5.7 3.6.9 3.7.4 3.8.0 pypy3.5-7.0.0 pypy3.6-7.1.1" git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv pushd /hgdev/pyenv -git checkout 3faeda67bb33e07750d1a104271369a7384ca45c +git checkout d6d6bc8bb08bcdcbf4eb79509aa7061011ade1c4 popd export PYENV_ROOT="/hgdev/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" -# pip 19.0.3. -PIP_SHA256=efe99298f3fbb1f56201ce6b81d2658067d2f7d7dfc2d412e0d3cacc9a397c61 -wget -O get-pip.py --progress dot:mega https://github.com/pypa/get-pip/raw/fee32c376da1ff6496a798986d7939cd51e1644f/get-pip.py +# pip 19.2.3. +PIP_SHA256=57e3643ff19f018f8a00dfaa6b7e4620e3c1a7a2171fd218425366ec006b3bfe +wget -O get-pip.py --progress dot:mega https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py echo "${PIP_SHA256} get-pip.py" | sha256sum --check - -VIRTUALENV_SHA256=984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39 -VIRTUALENV_TARBALL=virtualenv-16.4.3.tar.gz -wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/${VIRTUALENV_TARBALL} +VIRTUALENV_SHA256=f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2 +VIRTUALENV_TARBALL=virtualenv-16.7.5.tar.gz +wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/${VIRTUALENV_TARBALL} echo "${VIRTUALENV_SHA256} ${VIRTUALENV_TARBALL}" | sha256sum --check - for v in ${PYENV2_VERSIONS}; do @@ -62,23 +60,40 @@ for v in ${PYENV3_VERSIONS}; do done pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system -'''.lstrip().replace('\r\n', '\n') +'''.lstrip().replace( + '\r\n', '\n' +) + + +INSTALL_RUST = r''' +RUSTUP_INIT_SHA256=a46fe67199b7bcbbde2dcbc23ae08db6f29883e260e23899a88b9073effc9076 +wget -O rustup-init --progress dot:mega https://static.rust-lang.org/rustup/archive/1.18.3/x86_64-unknown-linux-gnu/rustup-init +echo "${RUSTUP_INIT_SHA256} rustup-init" | sha256sum --check - + +chmod +x rustup-init +sudo -H -u hg -g hg ./rustup-init -y +sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.34.2 +sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy +''' BOOTSTRAP_VIRTUALENV = r''' /usr/bin/virtualenv /hgdev/venv-bootstrap -HG_SHA256=1bdd21bb87d1e05fb5cd395d488d0e0cc2f2f90ce0fd248e31a03595da5ccb47 -HG_TARBALL=mercurial-4.9.1.tar.gz +HG_SHA256=35fc8ba5e0379c1b3affa2757e83fb0509e8ac314cbd9f1fd133cf265d16e49f +HG_TARBALL=mercurial-5.1.1.tar.gz wget -O ${HG_TARBALL} --progress dot:mega https://www.mercurial-scm.org/release/${HG_TARBALL} echo "${HG_SHA256} ${HG_TARBALL}" | sha256sum --check - /hgdev/venv-bootstrap/bin/pip install ${HG_TARBALL} -'''.lstrip().replace('\r\n', '\n') +'''.lstrip().replace( + '\r\n', '\n' +) -BOOTSTRAP_DEBIAN = r''' +BOOTSTRAP_DEBIAN = ( + r''' #!/bin/bash set -ex @@ -175,18 +190,22 @@ EOF sudo apt-key add docker-apt-key -if [ "$DEBIAN_VERSION" = "9.8" ]; then +if [ "$LSB_RELEASE" = "stretch" ]; then cat << EOF | sudo tee -a /etc/apt/sources.list # Need backports for clang-format-6.0 deb http://deb.debian.org/debian stretch-backports main +EOF +fi +if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "buster" ]; then +cat << EOF | sudo tee -a /etc/apt/sources.list # Sources are useful if we want to compile things locally. -deb-src http://deb.debian.org/debian stretch main -deb-src http://security.debian.org/debian-security stretch/updates main -deb-src http://deb.debian.org/debian stretch-updates main -deb-src http://deb.debian.org/debian stretch-backports main +deb-src http://deb.debian.org/debian $LSB_RELEASE main +deb-src http://security.debian.org/debian-security $LSB_RELEASE/updates main +deb-src http://deb.debian.org/debian $LSB_RELEASE-updates main +deb-src http://deb.debian.org/debian $LSB_RELEASE-backports main -deb [arch=amd64] https://download.docker.com/linux/debian stretch stable +deb [arch=amd64] https://download.docker.com/linux/debian $LSB_RELEASE stable EOF elif [ "$DISTRO" = "Ubuntu" ]; then @@ -199,6 +218,7 @@ fi sudo apt-get update PACKAGES="\ + awscli \ btrfs-progs \ build-essential \ bzr \ @@ -207,6 +227,7 @@ PACKAGES="\ darcs \ debhelper \ devscripts \ + docker-ce \ dpkg-dev \ dstat \ emacs \ @@ -239,6 +260,7 @@ PACKAGES="\ python-pygments \ python-subversion \ python-vcr \ + python3-boto3 \ python3-dev \ python3-docutils \ python3-fuzzywuzzy \ @@ -259,23 +281,17 @@ PACKAGES="\ zip \ zlib1g-dev" -if [ "$DEBIAN_VERSION" = "9.8" ]; then +if [ "LSB_RELEASE" = "stretch" ]; then PACKAGES="$PACKAGES linux-perf" elif [ "$DISTRO" = "Ubuntu" ]; then PACKAGES="$PACKAGES linux-tools-common" fi -# Ubuntu 19.04 removes monotone. -if [ "$LSB_RELEASE" != "disco" ]; then +# Monotone only available in older releases. +if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "xenial" ]; then PACKAGES="$PACKAGES monotone" fi -# As of April 27, 2019, Docker hasn't published packages for -# Ubuntu 19.04 yet. -if [ "$LSB_RELEASE" != "disco" ]; then - PACKAGES="$PACKAGES docker-ce" -fi - sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends $PACKAGES # Create clang-format symlink so test harness finds it. @@ -286,6 +302,8 @@ sudo mkdir /hgdev # Will be normalized to hg:hg later. sudo chown `whoami` /hgdev +{install_rust} + cp requirements-py2.txt /hgdev/requirements-py2.txt cp requirements-py3.txt /hgdev/requirements-py3.txt @@ -308,10 +326,14 @@ publish = false EOF sudo chown -R hg:hg /hgdev -'''.lstrip().format( - install_pythons=INSTALL_PYTHONS, - bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV -).replace('\r\n', '\n') +'''.lstrip() + .format( + install_rust=INSTALL_RUST, + install_pythons=INSTALL_PYTHONS, + bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV, + ) + .replace('\r\n', '\n') +) # Prepares /hgdev for operations. @@ -393,7 +415,9 @@ mkdir /hgwork/tmp chown hg:hg /hgwork/tmp rsync -a /hgdev/src /hgwork/ -'''.lstrip().replace('\r\n', '\n') +'''.lstrip().replace( + '\r\n', '\n' +) HG_UPDATE_CLEAN = ''' @@ -405,7 +429,9 @@ cd /hgwork/src ${HG} --config extensions.purge= purge --all ${HG} update -C $1 ${HG} log -r . -'''.lstrip().replace('\r\n', '\n') +'''.lstrip().replace( + '\r\n', '\n' +) def prepare_exec_environment(ssh_client, filesystem='default'): @@ -440,11 +466,12 @@ def prepare_exec_environment(ssh_client, res = chan.recv_exit_status() if res: - raise Exception('non-0 exit code updating working directory; %d' - % res) + raise Exception('non-0 exit code updating working directory; %d' % res) -def synchronize_hg(source_path: pathlib.Path, ec2_instance, revision: str=None): +def synchronize_hg( + source_path: pathlib.Path, ec2_instance, revision: str = None +): """Synchronize a local Mercurial source path to remote EC2 instance.""" with tempfile.TemporaryDirectory() as temp_dir: @@ -466,8 +493,10 @@ def synchronize_hg(source_path: pathlib. fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path) if not (source_path / '.hg').is_dir(): - raise Exception('%s is not a Mercurial repository; synchronization ' - 'not yet supported' % source_path) + raise Exception( + '%s is not a Mercurial repository; synchronization ' + 'not yet supported' % source_path + ) env = dict(os.environ) env['HGPLAIN'] = '1' @@ -477,17 +506,29 @@ def synchronize_hg(source_path: pathlib. res = subprocess.run( ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], - cwd=str(source_path), env=env, check=True, capture_output=True) + cwd=str(source_path), + env=env, + check=True, + capture_output=True, + ) full_revision = res.stdout.decode('ascii') args = [ - 'python2.7', str(hg_bin), - '--config', 'ui.ssh=ssh -F %s' % ssh_config, - '--config', 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg', + 'python2.7', + str(hg_bin), + '--config', + 'ui.ssh=ssh -F %s' % ssh_config, + '--config', + 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg', # Also ensure .hgtags changes are present so auto version # calculation works. - 'push', '-f', '-r', full_revision, '-r', 'file(.hgtags)', + 'push', + '-f', + '-r', + full_revision, + '-r', + 'file(.hgtags)', 'ssh://%s//hgwork/src' % public_ip, ] @@ -506,7 +547,8 @@ def synchronize_hg(source_path: pathlib. fh.chmod(0o0700) chan, stdin, stdout = exec_command( - ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision) + ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision + ) stdin.close() for line in stdout: @@ -515,8 +557,9 @@ def synchronize_hg(source_path: pathlib. res = chan.recv_exit_status() if res: - raise Exception('non-0 exit code updating working directory; %d' - % res) + raise Exception( + 'non-0 exit code updating working directory; %d' % res + ) def run_tests(ssh_client, python_version, test_flags=None): @@ -538,8 +581,8 @@ def run_tests(ssh_client, python_version command = ( '/bin/sh -c "export TMPDIR=/hgwork/tmp; ' - 'cd /hgwork/src/tests && %s run-tests.py %s"' % ( - python, test_flags)) + 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags) + ) chan, stdin, stdout = exec_command(ssh_client, command) diff --git a/contrib/automation/hgautomation/pypi.py b/contrib/automation/hgautomation/pypi.py new file mode 100644 --- /dev/null +++ b/contrib/automation/hgautomation/pypi.py @@ -0,0 +1,21 @@ +# pypi.py - Automation around PyPI +# +# Copyright 2019 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# no-check-code because Python 3 native. + +from twine.commands.upload import upload as twine_upload +from twine.settings import Settings + + +def upload(paths): + """Upload files to PyPI. + + `paths` is an iterable of `pathlib.Path`. + """ + settings = Settings() + + twine_upload(settings, [str(p) for p in paths]) diff --git a/contrib/automation/hgautomation/ssh.py b/contrib/automation/hgautomation/ssh.py --- a/contrib/automation/hgautomation/ssh.py +++ b/contrib/automation/hgautomation/ssh.py @@ -11,14 +11,13 @@ import socket import time import warnings -from cryptography.utils import ( - CryptographyDeprecationWarning, -) +from cryptography.utils import CryptographyDeprecationWarning import paramiko def wait_for_ssh(hostname, port, timeout=60, username=None, key_filename=None): """Wait for an SSH server to start on the specified host and port.""" + class IgnoreHostKeyPolicy(paramiko.MissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): return @@ -28,17 +27,23 @@ def wait_for_ssh(hostname, port, timeout # paramiko triggers a CryptographyDeprecationWarning in the cryptography # package. Let's suppress with warnings.catch_warnings(): - warnings.filterwarnings('ignore', - category=CryptographyDeprecationWarning) + warnings.filterwarnings( + 'ignore', category=CryptographyDeprecationWarning + ) while True: client = paramiko.SSHClient() client.set_missing_host_key_policy(IgnoreHostKeyPolicy()) try: - client.connect(hostname, port=port, username=username, - key_filename=key_filename, - timeout=5.0, allow_agent=False, - look_for_keys=False) + client.connect( + hostname, + port=port, + username=username, + key_filename=key_filename, + timeout=5.0, + allow_agent=False, + look_for_keys=False, + ) return client except socket.error: diff --git a/contrib/automation/hgautomation/try_server.py b/contrib/automation/hgautomation/try_server.py new file mode 100644 --- /dev/null +++ b/contrib/automation/hgautomation/try_server.py @@ -0,0 +1,99 @@ +# try_server.py - Interact with Try server +# +# Copyright 2019 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# no-check-code because Python 3 native. + +import base64 +import json +import os +import subprocess +import tempfile + +from .aws import AWSConnection + +LAMBDA_FUNCTION = "ci-try-server-upload" + + +def trigger_try(c: AWSConnection, rev="."): + """Trigger a new Try run.""" + lambda_client = c.session.client("lambda") + + cset, bundle = generate_bundle(rev=rev) + + payload = { + "bundle": base64.b64encode(bundle).decode("utf-8"), + "node": cset["node"], + "branch": cset["branch"], + "user": cset["user"], + "message": cset["desc"], + } + + print("resolved revision:") + print("node: %s" % cset["node"]) + print("branch: %s" % cset["branch"]) + print("user: %s" % cset["user"]) + print("desc: %s" % cset["desc"].splitlines()[0]) + print() + + print("sending to Try...") + res = lambda_client.invoke( + FunctionName=LAMBDA_FUNCTION, + InvocationType="RequestResponse", + Payload=json.dumps(payload).encode("utf-8"), + ) + + body = json.load(res["Payload"]) + for message in body: + print("remote: %s" % message) + + +def generate_bundle(rev="."): + """Generate a bundle suitable for use by the Try service. + + Returns a tuple of revision metadata and raw Mercurial bundle data. + """ + # `hg bundle` doesn't support streaming to stdout. So we use a temporary + # file. + path = None + try: + fd, path = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg") + os.close(fd) + + args = [ + "hg", + "bundle", + "--type", + "gzip-v2", + "--base", + "public()", + "--rev", + rev, + path, + ] + + print("generating bundle...") + subprocess.run(args, check=True) + + with open(path, "rb") as fh: + bundle_data = fh.read() + + finally: + if path: + os.unlink(path) + + args = [ + "hg", + "log", + "-r", + rev, + # We have to upload as JSON, so it won't matter if we emit binary + # since we need to normalize to UTF-8. + "-T", + "json", + ] + res = subprocess.run(args, check=True, capture_output=True) + return json.loads(res.stdout)[0], bundle_data diff --git a/contrib/automation/hgautomation/windows.py b/contrib/automation/hgautomation/windows.py --- a/contrib/automation/hgautomation/windows.py +++ b/contrib/automation/hgautomation/windows.py @@ -7,15 +7,16 @@ # no-check-code because Python 3 native. +import datetime import os +import paramiko import pathlib import re import subprocess import tempfile -from .winrm import ( - run_powershell, -) +from .pypi import upload as pypi_upload +from .winrm import run_powershell # PowerShell commands to activate a Visual Studio 2008 environment. @@ -100,6 +101,33 @@ if ($LASTEXITCODE -ne 0) {{ }} ''' +X86_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win32.whl' +X64_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' +X86_EXE_FILENAME = 'Mercurial-{version}.exe' +X64_EXE_FILENAME = 'Mercurial-{version}-x64.exe' +X86_MSI_FILENAME = 'mercurial-{version}-x86.msi' +X64_MSI_FILENAME = 'mercurial-{version}-x64.msi' + +MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' + +X86_USER_AGENT_PATTERN = '.*Windows.*' +X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' + +X86_EXE_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x86 Windows ' + '- does not require admin rights' +) +X64_EXE_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x64 Windows ' + '- does not require admin rights' +) +X86_MSI_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights' +) +X64_MSI_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights' +) + def get_vc_prefix(arch): if arch == 'x86': @@ -133,10 +161,21 @@ def synchronize_hg(hg_repo: pathlib.Path ssh_dir.chmod(0o0700) # Generate SSH key to use for communication. - subprocess.run([ - 'ssh-keygen', '-t', 'rsa', '-b', '4096', '-N', '', - '-f', str(ssh_dir / 'id_rsa')], - check=True, capture_output=True) + subprocess.run( + [ + 'ssh-keygen', + '-t', + 'rsa', + '-b', + '4096', + '-N', + '', + '-f', + str(ssh_dir / 'id_rsa'), + ], + check=True, + capture_output=True, + ) # Add it to ~/.ssh/authorized_keys on remote. # This assumes the file doesn't already exist. @@ -157,8 +196,10 @@ def synchronize_hg(hg_repo: pathlib.Path fh.write(' IdentityFile %s\n' % (ssh_dir / 'id_rsa')) if not (hg_repo / '.hg').is_dir(): - raise Exception('%s is not a Mercurial repository; ' - 'synchronization not yet supported' % hg_repo) + raise Exception( + '%s is not a Mercurial repository; ' + 'synchronization not yet supported' % hg_repo + ) env = dict(os.environ) env['HGPLAIN'] = '1' @@ -168,17 +209,29 @@ def synchronize_hg(hg_repo: pathlib.Path res = subprocess.run( ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], - cwd=str(hg_repo), env=env, check=True, capture_output=True) + cwd=str(hg_repo), + env=env, + check=True, + capture_output=True, + ) full_revision = res.stdout.decode('ascii') args = [ - 'python2.7', hg_bin, - '--config', 'ui.ssh=ssh -F %s' % ssh_config, - '--config', 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', + 'python2.7', + hg_bin, + '--config', + 'ui.ssh=ssh -F %s' % ssh_config, + '--config', + 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', # Also ensure .hgtags changes are present so auto version # calculation works. - 'push', '-f', '-r', full_revision, '-r', 'file(.hgtags)', + 'push', + '-f', + '-r', + full_revision, + '-r', + 'file(.hgtags)', 'ssh://%s/c:/hgdev/src' % public_ip, ] @@ -188,8 +241,9 @@ def synchronize_hg(hg_repo: pathlib.Path if res.returncode not in (0, 1): res.check_returncode() - run_powershell(winrm_client, - HG_UPDATE_CLEAN.format(revision=full_revision)) + run_powershell( + winrm_client, HG_UPDATE_CLEAN.format(revision=full_revision) + ) # TODO detect dirty local working directory and synchronize accordingly. @@ -225,8 +279,9 @@ def copy_latest_dist(winrm_client, patte winrm_client.fetch(source, str(dest)) -def build_inno_installer(winrm_client, arch: str, dest_path: pathlib.Path, - version=None): +def build_inno_installer( + winrm_client, arch: str, dest_path: pathlib.Path, version=None +): """Build the Inno Setup installer on a remote machine. Using a WinRM client, remote commands are executed to build @@ -238,8 +293,9 @@ def build_inno_installer(winrm_client, a if version: extra_args.extend(['--version', version]) - ps = get_vc_prefix(arch) + BUILD_INNO.format(arch=arch, - extra_args=' '.join(extra_args)) + ps = get_vc_prefix(arch) + BUILD_INNO.format( + arch=arch, extra_args=' '.join(extra_args) + ) run_powershell(winrm_client, ps) copy_latest_dist(winrm_client, '*.exe', dest_path) @@ -256,8 +312,9 @@ def build_wheel(winrm_client, arch: str, copy_latest_dist(winrm_client, '*.whl', dest_path) -def build_wix_installer(winrm_client, arch: str, dest_path: pathlib.Path, - version=None): +def build_wix_installer( + winrm_client, arch: str, dest_path: pathlib.Path, version=None +): """Build the WiX installer on a remote machine. Using a WinRM client, remote commands are executed to build a WiX installer. @@ -267,8 +324,9 @@ def build_wix_installer(winrm_client, ar if version: extra_args.extend(['--version', version]) - ps = get_vc_prefix(arch) + BUILD_WIX.format(arch=arch, - extra_args=' '.join(extra_args)) + ps = get_vc_prefix(arch) + BUILD_WIX.format( + arch=arch, extra_args=' '.join(extra_args) + ) run_powershell(winrm_client, ps) copy_latest_dist(winrm_client, '*.msi', dest_path) @@ -282,17 +340,171 @@ def run_tests(winrm_client, python_versi ``run-tests.py``. """ if not re.match(r'\d\.\d', python_version): - raise ValueError(r'python_version must be \d.\d; got %s' % - python_version) + raise ValueError( + r'python_version must be \d.\d; got %s' % python_version + ) if arch not in ('x86', 'x64'): raise ValueError('arch must be x86 or x64; got %s' % arch) python_path = 'python%s-%s' % (python_version.replace('.', ''), arch) - ps = RUN_TESTS.format( - python_path=python_path, - test_flags=test_flags or '', + ps = RUN_TESTS.format(python_path=python_path, test_flags=test_flags or '',) + + run_powershell(winrm_client, ps) + + +def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): + return ( + dist_path / X86_WHEEL_FILENAME.format(version=version), + dist_path / X64_WHEEL_FILENAME.format(version=version), + ) + + +def resolve_all_artifacts(dist_path: pathlib.Path, version: str): + return ( + dist_path / X86_WHEEL_FILENAME.format(version=version), + dist_path / X64_WHEEL_FILENAME.format(version=version), + dist_path / X86_EXE_FILENAME.format(version=version), + dist_path / X64_EXE_FILENAME.format(version=version), + dist_path / X86_MSI_FILENAME.format(version=version), + dist_path / X64_MSI_FILENAME.format(version=version), + ) + + +def generate_latest_dat(version: str): + x86_exe_filename = X86_EXE_FILENAME.format(version=version) + x64_exe_filename = X64_EXE_FILENAME.format(version=version) + x86_msi_filename = X86_MSI_FILENAME.format(version=version) + x64_msi_filename = X64_MSI_FILENAME.format(version=version) + + entries = ( + ( + '10', + version, + X86_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename), + X86_EXE_DESCRIPTION.format(version=version), + ), + ( + '10', + version, + X64_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename), + X64_EXE_DESCRIPTION.format(version=version), + ), + ( + '10', + version, + X86_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename), + X86_MSI_DESCRIPTION.format(version=version), + ), + ( + '10', + version, + X64_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), + X64_MSI_DESCRIPTION.format(version=version), + ), ) - run_powershell(winrm_client, ps) + lines = ['\t'.join(e) for e in entries] + + return '\n'.join(lines) + '\n' + + +def publish_artifacts_pypi(dist_path: pathlib.Path, version: str): + """Publish Windows release artifacts to PyPI.""" + + wheel_paths = resolve_wheel_artifacts(dist_path, version) + + for p in wheel_paths: + if not p.exists(): + raise Exception('%s not found' % p) + + print('uploading wheels to PyPI (you may be prompted for credentials)') + pypi_upload(wheel_paths) + + +def publish_artifacts_mercurial_scm_org( + dist_path: pathlib.Path, version: str, ssh_username=None +): + """Publish Windows release artifacts to mercurial-scm.org.""" + all_paths = resolve_all_artifacts(dist_path, version) + + for p in all_paths: + if not p.exists(): + raise Exception('%s not found' % p) + + client = paramiko.SSHClient() + client.load_system_host_keys() + # We assume the system SSH configuration knows how to connect. + print('connecting to mercurial-scm.org via ssh...') + try: + client.connect('mercurial-scm.org', username=ssh_username) + except paramiko.AuthenticationException: + print('error authenticating; is an SSH key available in an SSH agent?') + raise + + print('SSH connection established') + + print('opening SFTP client...') + sftp = client.open_sftp() + print('SFTP client obtained') + + for p in all_paths: + dest_path = '/var/www/release/windows/%s' % p.name + print('uploading %s to %s' % (p, dest_path)) + + with p.open('rb') as fh: + data = fh.read() + + with sftp.open(dest_path, 'wb') as fh: + fh.write(data) + fh.chmod(0o0664) + + latest_dat_path = '/var/www/release/windows/latest.dat' + + now = datetime.datetime.utcnow() + backup_path = dist_path / ( + 'latest-windows-%s.dat' % now.strftime('%Y%m%dT%H%M%S') + ) + print('backing up %s to %s' % (latest_dat_path, backup_path)) + + with sftp.open(latest_dat_path, 'rb') as fh: + latest_dat_old = fh.read() + + with backup_path.open('wb') as fh: + fh.write(latest_dat_old) + + print('writing %s with content:' % latest_dat_path) + latest_dat_content = generate_latest_dat(version) + print(latest_dat_content) + + with sftp.open(latest_dat_path, 'wb') as fh: + fh.write(latest_dat_content.encode('ascii')) + + +def publish_artifacts( + dist_path: pathlib.Path, + version: str, + pypi=True, + mercurial_scm_org=True, + ssh_username=None, +): + """Publish Windows release artifacts. + + Files are found in `dist_path`. We will look for files with version string + `version`. + + `pypi` controls whether we upload to PyPI. + `mercurial_scm_org` controls whether we upload to mercurial-scm.org. + """ + if pypi: + publish_artifacts_pypi(dist_path, version) + + if mercurial_scm_org: + publish_artifacts_mercurial_scm_org( + dist_path, version, ssh_username=ssh_username + ) diff --git a/contrib/automation/hgautomation/winrm.py b/contrib/automation/hgautomation/winrm.py --- a/contrib/automation/hgautomation/winrm.py +++ b/contrib/automation/hgautomation/winrm.py @@ -11,9 +11,7 @@ import logging import pprint import time -from pypsrp.client import ( - Client, -) +from pypsrp.client import Client from pypsrp.powershell import ( PowerShell, PSInvocationState, @@ -35,8 +33,13 @@ def wait_for_winrm(host, username, passw while True: try: - client = Client(host, username=username, password=password, - ssl=ssl, connection_timeout=5) + client = Client( + host, + username=username, + password=password, + ssl=ssl, + connection_timeout=5, + ) client.execute_ps("Write-Host 'Hello, World!'") return client except requests.exceptions.ConnectionError: @@ -52,7 +55,7 @@ def format_object(o): try: o = str(o) - except TypeError: + except (AttributeError, TypeError): o = pprint.pformat(o.extended_properties) return o @@ -78,5 +81,7 @@ def run_powershell(client, script): print(format_object(o)) if ps.state == PSInvocationState.FAILED: - raise Exception('PowerShell execution failed: %s' % - ' '.join(map(format_object, ps.streams.error))) + raise Exception( + 'PowerShell execution failed: %s' + % ' '.join(map(format_object, ps.streams.error)) + ) diff --git a/contrib/automation/linux-requirements-py2.txt b/contrib/automation/linux-requirements-py2.txt --- a/contrib/automation/linux-requirements-py2.txt +++ b/contrib/automation/linux-requirements-py2.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile -U --generate-hashes --output-file contrib/automation/linux-requirements-py2.txt contrib/automation/linux-requirements.txt.in +# pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py2.txt contrib/automation/linux-requirements.txt.in # astroid==1.6.6 \ --hash=sha256:87de48a92e29cedf7210ffa853d11441e7ad94cb47bacd91b023499b51cbc756 \ @@ -22,10 +22,10 @@ contextlib2==0.5.5 \ --hash=sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48 \ --hash=sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00 \ # via vcrpy -docutils==0.14 \ - --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ - --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ - --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ @@ -36,83 +36,70 @@ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 \ # via mock -futures==3.2.0 \ - --hash=sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265 \ - --hash=sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1 \ +futures==3.3.0 \ + --hash=sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16 \ + --hash=sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794 \ # via isort fuzzywuzzy==0.17.0 \ --hash=sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254 \ --hash=sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62 -isort==4.3.17 \ - --hash=sha256:01cb7e1ca5e6c5b3f235f0385057f70558b70d2f00320208825fa62887292f43 \ - --hash=sha256:268067462aed7eb2a1e237fcb287852f22077de3fb07964e87e00f829eea2d1a \ +isort==4.3.21 \ + --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \ + --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd \ # via pylint -lazy-object-proxy==1.3.1 \ - --hash=sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33 \ - --hash=sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39 \ - --hash=sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019 \ - --hash=sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088 \ - --hash=sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b \ - --hash=sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e \ - --hash=sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6 \ - --hash=sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b \ - --hash=sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5 \ - --hash=sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff \ - --hash=sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd \ - --hash=sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7 \ - --hash=sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff \ - --hash=sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d \ - --hash=sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2 \ - --hash=sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35 \ - --hash=sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4 \ - --hash=sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514 \ - --hash=sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252 \ - --hash=sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109 \ - --hash=sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f \ - --hash=sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c \ - --hash=sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92 \ - --hash=sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577 \ - --hash=sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d \ - --hash=sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d \ - --hash=sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f \ - --hash=sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a \ - --hash=sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b \ +lazy-object-proxy==1.4.1 \ + --hash=sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661 \ + --hash=sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f \ + --hash=sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13 \ + --hash=sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821 \ + --hash=sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71 \ + --hash=sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e \ + --hash=sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea \ + --hash=sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229 \ + --hash=sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4 \ + --hash=sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e \ + --hash=sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20 \ + --hash=sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16 \ + --hash=sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b \ + --hash=sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7 \ + --hash=sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c \ + --hash=sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a \ + --hash=sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e \ + --hash=sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1 \ # via astroid mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \ # via pylint -mock==2.0.0 \ - --hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \ - --hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba \ +mock==3.0.5 \ + --hash=sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3 \ + --hash=sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8 \ # via vcrpy -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 \ - # via mock pyflakes==2.1.1 \ --hash=sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0 \ --hash=sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2 -pygments==2.3.1 \ - --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ - --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d -pylint==1.9.4 \ - --hash=sha256:02c2b6d268695a8b64ad61847f92e611e6afcff33fd26c3a2125370c4662905d \ - --hash=sha256:ee1e85575587c5b58ddafa25e1c1b01691ef172e139fc25585e5d3f02451da93 +pygments==2.4.2 \ + --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ + --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 +pylint==1.9.5 \ + --hash=sha256:367e3d49813d349a905390ac27989eff82ab84958731c5ef0bef867452cfdc42 \ + --hash=sha256:97a42df23d436c70132971d1dcb9efad2fe5c0c6add55b90161e773caf729300 python-levenshtein==0.12.0 \ --hash=sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1 -pyyaml==5.1 \ - --hash=sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c \ - --hash=sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95 \ - --hash=sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2 \ - --hash=sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4 \ - --hash=sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad \ - --hash=sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba \ - --hash=sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1 \ - --hash=sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e \ - --hash=sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673 \ - --hash=sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13 \ - --hash=sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19 \ +pyyaml==5.1.2 \ + --hash=sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9 \ + --hash=sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4 \ + --hash=sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8 \ + --hash=sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696 \ + --hash=sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34 \ + --hash=sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9 \ + --hash=sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73 \ + --hash=sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299 \ + --hash=sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b \ + --hash=sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae \ + --hash=sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681 \ + --hash=sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41 \ + --hash=sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8 \ # via vcrpy singledispatch==3.4.0.3 \ --hash=sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c \ @@ -125,6 +112,10 @@ six==1.12.0 \ vcrpy==2.0.1 \ --hash=sha256:127e79cf7b569d071d1bd761b83f7b62b2ce2a2eb63ceca7aa67cba8f2602ea3 \ --hash=sha256:57be64aa8e9883a4117d0b15de28af62275c001abcdb00b6dc2d4406073d9a4f -wrapt==1.11.1 \ - --hash=sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533 \ +wrapt==1.11.2 \ + --hash=sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1 \ # via astroid, vcrpy + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# setuptools==41.0.1 # via python-levenshtein diff --git a/contrib/automation/linux-requirements-py3.txt b/contrib/automation/linux-requirements-py3.txt --- a/contrib/automation/linux-requirements-py3.txt +++ b/contrib/automation/linux-requirements-py3.txt @@ -2,16 +2,16 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile -U --generate-hashes --output-file contrib/automation/linux-requirements-py3.txt contrib/automation/linux-requirements.txt.in +# pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py3.txt contrib/automation/linux-requirements.txt.in # astroid==2.2.5 \ --hash=sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4 \ --hash=sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4 \ # via pylint -docutils==0.14 \ - --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ - --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ - --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 fuzzywuzzy==0.17.0 \ --hash=sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254 \ --hash=sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62 @@ -19,40 +19,29 @@ idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ # via yarl -isort==4.3.17 \ - --hash=sha256:01cb7e1ca5e6c5b3f235f0385057f70558b70d2f00320208825fa62887292f43 \ - --hash=sha256:268067462aed7eb2a1e237fcb287852f22077de3fb07964e87e00f829eea2d1a \ +isort==4.3.21 \ + --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \ + --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd \ # via pylint -lazy-object-proxy==1.3.1 \ - --hash=sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33 \ - --hash=sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39 \ - --hash=sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019 \ - --hash=sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088 \ - --hash=sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b \ - --hash=sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e \ - --hash=sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6 \ - --hash=sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b \ - --hash=sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5 \ - --hash=sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff \ - --hash=sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd \ - --hash=sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7 \ - --hash=sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff \ - --hash=sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d \ - --hash=sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2 \ - --hash=sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35 \ - --hash=sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4 \ - --hash=sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514 \ - --hash=sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252 \ - --hash=sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109 \ - --hash=sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f \ - --hash=sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c \ - --hash=sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92 \ - --hash=sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577 \ - --hash=sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d \ - --hash=sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d \ - --hash=sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f \ - --hash=sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a \ - --hash=sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b \ +lazy-object-proxy==1.4.1 \ + --hash=sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661 \ + --hash=sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f \ + --hash=sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13 \ + --hash=sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821 \ + --hash=sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71 \ + --hash=sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e \ + --hash=sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea \ + --hash=sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229 \ + --hash=sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4 \ + --hash=sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e \ + --hash=sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20 \ + --hash=sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16 \ + --hash=sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b \ + --hash=sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7 \ + --hash=sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c \ + --hash=sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a \ + --hash=sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e \ + --hash=sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1 \ # via astroid mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ @@ -92,57 +81,54 @@ multidict==4.5.2 \ pyflakes==2.1.1 \ --hash=sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0 \ --hash=sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2 -pygments==2.3.1 \ - --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ - --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d +pygments==2.4.2 \ + --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ + --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 pylint==2.3.1 \ --hash=sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09 \ --hash=sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1 python-levenshtein==0.12.0 \ --hash=sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1 -pyyaml==5.1 \ - --hash=sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c \ - --hash=sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95 \ - --hash=sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2 \ - --hash=sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4 \ - --hash=sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad \ - --hash=sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba \ - --hash=sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1 \ - --hash=sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e \ - --hash=sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673 \ - --hash=sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13 \ - --hash=sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19 \ +pyyaml==5.1.2 \ + --hash=sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9 \ + --hash=sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4 \ + --hash=sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8 \ + --hash=sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696 \ + --hash=sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34 \ + --hash=sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9 \ + --hash=sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73 \ + --hash=sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299 \ + --hash=sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b \ + --hash=sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae \ + --hash=sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681 \ + --hash=sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41 \ + --hash=sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8 \ # via vcrpy six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ # via astroid, vcrpy -typed-ast==1.3.4 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \ - --hash=sha256:04894d268ba6eab7e093d43107869ad49e7b5ef40d1a94243ea49b352061b200 \ - --hash=sha256:16616ece19daddc586e499a3d2f560302c11f122b9c692bc216e821ae32aa0d0 \ - --hash=sha256:252fdae740964b2d3cdfb3f84dcb4d6247a48a6abe2579e8029ab3be3cdc026c \ - --hash=sha256:2af80a373af123d0b9f44941a46df67ef0ff7a60f95872412a145f4500a7fc99 \ - --hash=sha256:2c88d0a913229a06282b285f42a31e063c3bf9071ff65c5ea4c12acb6977c6a7 \ - --hash=sha256:2ea99c029ebd4b5a308d915cc7fb95b8e1201d60b065450d5d26deb65d3f2bc1 \ - --hash=sha256:3d2e3ab175fc097d2a51c7a0d3fda442f35ebcc93bb1d7bd9b95ad893e44c04d \ - --hash=sha256:4766dd695548a15ee766927bf883fb90c6ac8321be5a60c141f18628fb7f8da8 \ - --hash=sha256:56b6978798502ef66625a2e0f80cf923da64e328da8bbe16c1ff928c70c873de \ - --hash=sha256:5cddb6f8bce14325b2863f9d5ac5c51e07b71b462361fd815d1d7706d3a9d682 \ - --hash=sha256:644ee788222d81555af543b70a1098f2025db38eaa99226f3a75a6854924d4db \ - --hash=sha256:64cf762049fc4775efe6b27161467e76d0ba145862802a65eefc8879086fc6f8 \ - --hash=sha256:68c362848d9fb71d3c3e5f43c09974a0ae319144634e7a47db62f0f2a54a7fa7 \ - --hash=sha256:6c1f3c6f6635e611d58e467bf4371883568f0de9ccc4606f17048142dec14a1f \ - --hash=sha256:b213d4a02eec4ddf622f4d2fbc539f062af3788d1f332f028a2e19c42da53f15 \ - --hash=sha256:bb27d4e7805a7de0e35bd0cb1411bc85f807968b2b0539597a49a23b00a622ae \ - --hash=sha256:c9d414512eaa417aadae7758bc118868cd2396b0e6138c1dd4fda96679c079d3 \ - --hash=sha256:f0937165d1e25477b01081c4763d2d9cdc3b18af69cb259dd4f640c9b900fe5e \ - --hash=sha256:fb96a6e2c11059ecf84e6741a319f93f683e440e341d4489c9b161eca251cf2a \ - --hash=sha256:fc71d2d6ae56a091a8d94f33ec9d0f2001d1cb1db423d8b4355debfe9ce689b7 +typed-ast==1.4.0 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \ + --hash=sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e \ + --hash=sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e \ + --hash=sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0 \ + --hash=sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c \ + --hash=sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631 \ + --hash=sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4 \ + --hash=sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34 \ + --hash=sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b \ + --hash=sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a \ + --hash=sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233 \ + --hash=sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1 \ + --hash=sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36 \ + --hash=sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d \ + --hash=sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a \ + --hash=sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12 vcrpy==2.0.1 \ --hash=sha256:127e79cf7b569d071d1bd761b83f7b62b2ce2a2eb63ceca7aa67cba8f2602ea3 \ --hash=sha256:57be64aa8e9883a4117d0b15de28af62275c001abcdb00b6dc2d4406073d9a4f -wrapt==1.11.1 \ - --hash=sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533 \ +wrapt==1.11.2 \ + --hash=sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1 \ # via astroid, vcrpy yarl==1.3.0 \ --hash=sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9 \ @@ -157,3 +143,7 @@ yarl==1.3.0 \ --hash=sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8 \ --hash=sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1 \ # via vcrpy + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# setuptools==41.0.1 # via python-levenshtein diff --git a/contrib/automation/requirements.txt b/contrib/automation/requirements.txt --- a/contrib/automation/requirements.txt +++ b/contrib/automation/requirements.txt @@ -2,43 +2,44 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile -U --generate-hashes --output-file contrib/automation/requirements.txt contrib/automation/requirements.txt.in +# pip-compile --generate-hashes --output-file=contrib/automation/requirements.txt contrib/automation/requirements.txt.in # -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 \ +asn1crypto==1.0.1 \ + --hash=sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292 \ + --hash=sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f \ # via cryptography -bcrypt==3.1.6 \ - --hash=sha256:0ba875eb67b011add6d8c5b76afbd92166e98b1f1efab9433d5dc0fafc76e203 \ - --hash=sha256:21ed446054c93e209434148ef0b362432bb82bbdaf7beef70a32c221f3e33d1c \ - --hash=sha256:28a0459381a8021f57230954b9e9a65bb5e3d569d2c253c5cac6cb181d71cf23 \ - --hash=sha256:2aed3091eb6f51c26b7c2fad08d6620d1c35839e7a362f706015b41bd991125e \ - --hash=sha256:2fa5d1e438958ea90eaedbf8082c2ceb1a684b4f6c75a3800c6ec1e18ebef96f \ - --hash=sha256:3a73f45484e9874252002793518da060fb11eaa76c30713faa12115db17d1430 \ - --hash=sha256:3e489787638a36bb466cd66780e15715494b6d6905ffdbaede94440d6d8e7dba \ - --hash=sha256:44636759d222baa62806bbceb20e96f75a015a6381690d1bc2eda91c01ec02ea \ - --hash=sha256:678c21b2fecaa72a1eded0cf12351b153615520637efcadc09ecf81b871f1596 \ - --hash=sha256:75460c2c3786977ea9768d6c9d8957ba31b5fbeb0aae67a5c0e96aab4155f18c \ - --hash=sha256:8ac06fb3e6aacb0a95b56eba735c0b64df49651c6ceb1ad1cf01ba75070d567f \ - --hash=sha256:8fdced50a8b646fff8fa0e4b1c5fd940ecc844b43d1da5a980cb07f2d1b1132f \ - --hash=sha256:9b2c5b640a2da533b0ab5f148d87fb9989bf9bcb2e61eea6a729102a6d36aef9 \ - --hash=sha256:a9083e7fa9adb1a4de5ac15f9097eb15b04e2c8f97618f1b881af40abce382e1 \ - --hash=sha256:b7e3948b8b1a81c5a99d41da5fb2dc03ddb93b5f96fcd3fd27e643f91efa33e1 \ - --hash=sha256:b998b8ca979d906085f6a5d84f7b5459e5e94a13fc27c28a3514437013b6c2f6 \ - --hash=sha256:dd08c50bc6f7be69cd7ba0769acca28c846ec46b7a8ddc2acf4b9ac6f8a7457e \ - --hash=sha256:de5badee458544ab8125e63e39afeedfcf3aef6a6e2282ac159c95ae7472d773 \ - --hash=sha256:ede2a87333d24f55a4a7338a6ccdccf3eaa9bed081d1737e0db4dbd1a4f7e6b6 \ +bcrypt==3.1.7 \ + --hash=sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89 \ + --hash=sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42 \ + --hash=sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294 \ + --hash=sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161 \ + --hash=sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31 \ + --hash=sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5 \ + --hash=sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c \ + --hash=sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0 \ + --hash=sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de \ + --hash=sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e \ + --hash=sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052 \ + --hash=sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09 \ + --hash=sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105 \ + --hash=sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133 \ + --hash=sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7 \ + --hash=sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc \ # via paramiko -boto3==1.9.137 \ - --hash=sha256:882cc4869b47b51dae4b4a900769e72171ff00e0b6bca644b2d7a7ad7378f324 \ - --hash=sha256:cd503a7e7a04f1c14d2801f9727159dfa88c393b4004e98940fa4aa205d920c8 -botocore==1.12.137 \ - --hash=sha256:0d95794f6b1239c75e2c5f966221bcd4b68020fddb5676f757531eedbb612ed8 \ - --hash=sha256:3213cf48cf2ceee10fc3b93221f2cd1c38521cca7584f547d5c086213cc60f35 \ +bleach==3.1.0 \ + --hash=sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16 \ + --hash=sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa \ + # via readme-renderer +boto3==1.9.243 \ + --hash=sha256:404acbecef8f4912f18312fcfaffe7eba7f10b3b7adf7853bdba59cdf2275ebb \ + --hash=sha256:c6e5a7e4548ce7586c354ff633f2a66ba3c471d15a8ae6a30f873122ab04e1cf +botocore==1.12.243 \ + --hash=sha256:397585a7881230274afb8d1877ef69a661b0a311745cd324f14a052fb2a2863a \ + --hash=sha256:4496f8da89cb496462a831897ad248e13e431d9fa7e41e06d426fd6658ab6e59 \ # via boto3, s3transfer -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae \ +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef \ # via requests cffi==1.12.3 \ --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ @@ -74,32 +75,29 @@ chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ # via requests -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 \ +cryptography==2.7 \ + --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ + --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ + --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ + --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ + --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ + --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ + --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ + --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ + --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ + --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ + --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ + --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ + --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ + --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ + --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ + --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d \ # via paramiko, pypsrp -docutils==0.14 \ - --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ - --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ - --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 \ - # via botocore +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 \ + # via botocore, readme-renderer idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ @@ -108,20 +106,24 @@ jmespath==0.9.4 \ --hash=sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6 \ --hash=sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c \ # via boto3, botocore -ntlm-auth==1.3.0 \ - --hash=sha256:bb2fd03c665f0f62c5f65695b62dcdb07fb7a45df6ebc86c770be2054d6902dd \ - --hash=sha256:ce5b4483ed761f341a538a426a71a52e5a9cf5fd834ebef1d2090f9eef14b3f8 \ +ntlm-auth==1.4.0 \ + --hash=sha256:11f7a3cec38155b7cecdd9bbc8c37cd738d8012f0523b3f98d8caefe394feb97 \ + --hash=sha256:350f2389c8ee5517f47db55a36ac2f8efc9742a60a678d6e2caa92385bdcaa9a \ # via pypsrp -paramiko==2.4.2 \ - --hash=sha256:3c16b2bfb4c0d810b24c40155dbfd113c0521e7e6ee593d704e84b4c658a1f3b \ - --hash=sha256:a8975a7df3560c9f1e2b43dc54ebd40fd00a7017392ca5445ce7df409f900fcb -pyasn1==0.4.5 \ - --hash=sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7 \ - --hash=sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e \ - # via paramiko +paramiko==2.6.0 \ + --hash=sha256:99f0179bdc176281d21961a003ffdb2ec369daac1a1007241f53374e376576cf \ + --hash=sha256:f4b2edfa0d226b70bd4ca31ea7e389325990283da23465d572ed1f70a7583041 +pkginfo==1.5.0.1 \ + --hash=sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb \ + --hash=sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32 \ + # via twine pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \ # via cffi +pygments==2.4.2 \ + --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ + --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 \ + # via readme-renderer pynacl==1.3.0 \ --hash=sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255 \ --hash=sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c \ @@ -143,26 +145,49 @@ pynacl==1.3.0 \ --hash=sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1 \ --hash=sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0 \ # via paramiko -pypsrp==0.3.1 \ - --hash=sha256:309853380fe086090a03cc6662a778ee69b1cae355ae4a932859034fd76e9d0b \ - --hash=sha256:90f946254f547dc3493cea8493c819ab87e152a755797c93aa2668678ba8ae85 +pypsrp==0.4.0 \ + --hash=sha256:64b5bdd725a9744c821483b05ecd266f6417f4c6e90ee961a08838480f7d025e \ + --hash=sha256:f42919247fb80f7dc24c552560d7c24e754d15326030c9e3b7b94f51cfa4dc69 python-dateutil==2.8.0 \ --hash=sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb \ --hash=sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e \ # via botocore -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b \ - # via pypsrp -s3transfer==0.2.0 \ - --hash=sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e \ - --hash=sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021 \ +readme-renderer==24.0 \ + --hash=sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f \ + --hash=sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d \ + # via twine +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 \ + # via twine +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 \ + # via pypsrp, requests-toolbelt, twine +s3transfer==0.2.1 \ + --hash=sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d \ + --hash=sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba \ # via boto3 six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ - # via bcrypt, cryptography, pynacl, pypsrp, python-dateutil -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 \ + # via bcrypt, bleach, cryptography, pynacl, pypsrp, python-dateutil, readme-renderer +tqdm==4.36.1 \ + --hash=sha256:abc25d0ce2397d070ef07d8c7e706aede7920da163c64997585d42d3537ece3d \ + --hash=sha256:dd3fcca8488bb1d416aa7469d2f277902f26260c45aa86b667b074cd44b3b115 \ + # via twine +twine==2.0.0 \ + --hash=sha256:5319dd3e02ac73fcddcd94f035b9631589ab5d23e1f4699d57365199d85261e1 \ + --hash=sha256:9fe7091715c7576df166df8ef6654e61bada39571783f2fd415bdcba867c6993 +urllib3==1.25.6 \ + --hash=sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398 \ + --hash=sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86 \ # via botocore, requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 \ + # via bleach + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# setuptools==41.2.0 # via twine diff --git a/contrib/automation/requirements.txt.in b/contrib/automation/requirements.txt.in --- a/contrib/automation/requirements.txt.in +++ b/contrib/automation/requirements.txt.in @@ -1,3 +1,4 @@ boto3 paramiko pypsrp +twine diff --git a/contrib/bdiff-torture.py b/contrib/bdiff-torture.py --- a/contrib/bdiff-torture.py +++ b/contrib/bdiff-torture.py @@ -6,17 +6,23 @@ import sys from mercurial import ( mdiff, + pycompat, ) + def reducetest(a, b): tries = 0 reductions = 0 print("reducing...") while tries < 1000: - a2 = "\n".join(l for l in a.splitlines() - if random.randint(0, 100) > 0) + "\n" - b2 = "\n".join(l for l in b.splitlines() - if random.randint(0, 100) > 0) + "\n" + a2 = ( + "\n".join(l for l in a.splitlines() if random.randint(0, 100) > 0) + + "\n" + ) + b2 = ( + "\n".join(l for l in b.splitlines() if random.randint(0, 100) > 0) + + "\n" + ) if a2 == a and b2 == b: continue if a2 == b2: @@ -31,8 +37,7 @@ def reducetest(a, b): a = a2 b = b2 - print("reduced:", reductions, len(a) + len(b), - repr(a), repr(b)) + print("reduced:", reductions, len(a) + len(b), repr(a), repr(b)) try: test1(a, b) except Exception as inst: @@ -40,6 +45,7 @@ def reducetest(a, b): sys.exit(0) + def test1(a, b): d = mdiff.textdiff(a, b) if not d: @@ -48,23 +54,25 @@ def test1(a, b): if c != b: raise ValueError("bad") + def testwrap(a, b): try: test1(a, b) return except Exception as inst: - pass - print("exception:", inst) + print("exception:", inst) reducetest(a, b) + def test(a, b): testwrap(a, b) testwrap(b, a) + def rndtest(size, noise): a = [] src = " aaaaaaaabbbbccd" - for x in xrange(size): + for x in pycompat.xrange(size): a.append(src[random.randint(0, len(src) - 1)]) while True: @@ -82,6 +90,7 @@ def rndtest(size, noise): test(a, b) + maxvol = 10000 startsize = 2 while True: diff --git a/contrib/benchmarks/__init__.py b/contrib/benchmarks/__init__.py --- a/contrib/benchmarks/__init__.py +++ b/contrib/benchmarks/__init__.py @@ -44,15 +44,24 @@ from mercurial import ( util, ) -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), - os.path.pardir, os.path.pardir)) +basedir = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir) +) reposdir = os.environ['REPOS_DIR'] -reposnames = [name for name in os.listdir(reposdir) - if os.path.isdir(os.path.join(reposdir, name, ".hg"))] +reposnames = [ + name + for name in os.listdir(reposdir) + if os.path.isdir(os.path.join(reposdir, name, ".hg")) +] if not reposnames: raise ValueError("No repositories found in $REPO_DIR") -outputre = re.compile((r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys ' - r'\d+.\d+ \(best of \d+\)')) +outputre = re.compile( + ( + r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys ' + r'\d+.\d+ \(best of \d+\)' + ) +) + def runperfcommand(reponame, command, *args, **kwargs): os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "") @@ -63,8 +72,9 @@ def runperfcommand(reponame, command, *a else: ui = uimod.ui() repo = hg.repository(ui, os.path.join(reposdir, reponame)) - perfext = extensions.load(ui, 'perfext', - os.path.join(basedir, 'contrib', 'perf.py')) + perfext = extensions.load( + ui, 'perfext', os.path.join(basedir, 'contrib', 'perf.py') + ) cmd = getattr(perfext, command) ui.pushbuffer() cmd(ui, repo, *args, **kwargs) @@ -74,6 +84,7 @@ def runperfcommand(reponame, command, *a raise ValueError("Invalid output {0}".format(output)) return float(match.group(1)) + def perfbench(repos=reposnames, name=None, params=None): """decorator to declare ASV benchmark based on contrib/perf.py extension @@ -104,10 +115,12 @@ def perfbench(repos=reposnames, name=Non def wrapped(repo, *args): def perf(command, *a, **kw): return runperfcommand(repo, command, *a, **kw) + return func(perf, *args) wrapped.params = [p[1] for p in params] wrapped.param_names = [p[0] for p in params] wrapped.pretty_name = name return wrapped + return decorator diff --git a/contrib/benchmarks/perf.py b/contrib/benchmarks/perf.py --- a/contrib/benchmarks/perf.py +++ b/contrib/benchmarks/perf.py @@ -9,18 +9,22 @@ from __future__ import absolute_import from . import perfbench + @perfbench() def track_tags(perf): return perf("perftags") + @perfbench() def track_status(perf): return perf("perfstatus", unknown=False) + @perfbench(params=[('rev', ['1000', '10000', 'tip'])]) def track_manifest(perf, rev): return perf("perfmanifest", rev) + @perfbench() def track_heads(perf): return perf("perfheads") diff --git a/contrib/benchmarks/revset.py b/contrib/benchmarks/revset.py --- a/contrib/benchmarks/revset.py +++ b/contrib/benchmarks/revset.py @@ -18,15 +18,16 @@ import sys from . import basedir, perfbench + def createrevsetbenchmark(baseset, variants=None): if variants is None: # Default variants - variants = ["plain", "first", "last", "sort", "sort+first", - "sort+last"] - fname = "track_" + "_".join("".join([ - c if c in string.digits + string.letters else " " - for c in baseset - ]).split()) + variants = ["plain", "first", "last", "sort", "sort+first", "sort+last"] + fname = "track_" + "_".join( + "".join( + [c if c in string.digits + string.letters else " " for c in baseset] + ).split() + ) def wrap(fname, baseset): @perfbench(name=baseset, params=[("variant", variants)]) @@ -36,18 +37,21 @@ def createrevsetbenchmark(baseset, varia for var in variant.split("+"): revset = "%s(%s)" % (var, revset) return perf("perfrevset", revset) + f.__name__ = fname return f + return wrap(fname, baseset) + def initializerevsetbenchmarks(): mod = sys.modules[__name__] - with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), - 'rb') as fh: + with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), 'rb') as fh: for line in fh: baseset = line.strip() if baseset and not baseset.startswith('#'): func = createrevsetbenchmark(baseset) setattr(mod, func.__name__, func) + initializerevsetbenchmarks() diff --git a/contrib/byteify-strings.py b/contrib/byteify-strings.py --- a/contrib/byteify-strings.py +++ b/contrib/byteify-strings.py @@ -18,10 +18,13 @@ import tempfile import token import tokenize + def adjusttokenpos(t, ofs): """Adjust start/end column of the given token""" - return t._replace(start=(t.start[0], t.start[1] + ofs), - end=(t.end[0], t.end[1] + ofs)) + return t._replace( + start=(t.start[0], t.start[1] + ofs), end=(t.end[0], t.end[1] + ofs) + ) + def replacetokens(tokens, opts): """Transform a stream of tokens from raw to Python 3. @@ -78,23 +81,68 @@ def replacetokens(tokens, opts): already been done. """ - st = tokens[j] - if st.type == token.STRING and st.string.startswith(("'", '"')): - sysstrtokens.add(st) + k = j + currtoken = tokens[k] + while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL): + k += 1 + if currtoken.type == token.STRING and currtoken.string.startswith( + ("'", '"') + ): + sysstrtokens.add(currtoken) + try: + currtoken = tokens[k] + except IndexError: + break + + def _isitemaccess(j): + """Assert the next tokens form an item access on `tokens[j]` and that + `tokens[j]` is a name. + """ + try: + return ( + tokens[j].type == token.NAME + and _isop(j + 1, '[') + and tokens[j + 2].type == token.STRING + and _isop(j + 3, ']') + ) + except IndexError: + return False + + def _ismethodcall(j, *methodnames): + """Assert the next tokens form a call to `methodname` with a string + as first argument on `tokens[j]` and that `tokens[j]` is a name. + """ + try: + return ( + tokens[j].type == token.NAME + and _isop(j + 1, '.') + and tokens[j + 2].type == token.NAME + and tokens[j + 2].string in methodnames + and _isop(j + 3, '(') + and tokens[j + 4].type == token.STRING + ) + except IndexError: + return False coldelta = 0 # column increment for new opening parens coloffset = -1 # column offset for the current line (-1: TBD) - parens = [(0, 0, 0)] # stack of (line, end-column, column-offset) + parens = [(0, 0, 0, -1)] # stack of (line, end-column, column-offset, type) + ignorenextline = False # don't transform the next line + insideignoreblock = False # don't transform until turned off for i, t in enumerate(tokens): # Compute the column offset for the current line, such that # the current line will be aligned to the last opening paren # as before. if coloffset < 0: - if t.start[1] == parens[-1][1]: - coloffset = parens[-1][2] - elif t.start[1] + 1 == parens[-1][1]: + lastparen = parens[-1] + if t.start[1] == lastparen[1]: + coloffset = lastparen[2] + elif t.start[1] + 1 == lastparen[1] and lastparen[3] not in ( + token.NEWLINE, + tokenize.NL, + ): # fix misaligned indent of s/util.Abort/error.Abort/ - coloffset = parens[-1][2] + (parens[-1][1] - t.start[1]) + coloffset = lastparen[2] + (lastparen[1] - t.start[1]) else: coloffset = 0 @@ -103,11 +151,26 @@ def replacetokens(tokens, opts): yield adjusttokenpos(t, coloffset) coldelta = 0 coloffset = -1 + if not insideignoreblock: + ignorenextline = ( + tokens[i - 1].type == token.COMMENT + and tokens[i - 1].string == "# no-py3-transform" + ) + continue + + if t.type == token.COMMENT: + if t.string == "# py3-transform: off": + insideignoreblock = True + if t.string == "# py3-transform: on": + insideignoreblock = False + + if ignorenextline or insideignoreblock: + yield adjusttokenpos(t, coloffset) continue # Remember the last paren position. if _isop(i, '(', '[', '{'): - parens.append(t.end + (coloffset + coldelta,)) + parens.append(t.end + (coloffset + coldelta, tokens[i + 1].type)) elif _isop(i, ')', ']', '}'): parens.pop() @@ -129,8 +192,10 @@ def replacetokens(tokens, opts): # components touching docstrings need to handle unicode, # unfortunately. if s[0:3] in ("'''", '"""'): - yield adjusttokenpos(t, coloffset) - continue + # If it's assigned to something, it's not a docstring + if not _isop(i - 1, '='): + yield adjusttokenpos(t, coloffset) + continue # If the first character isn't a quote, it is likely a string # prefixing character (such as 'b', 'u', or 'r'. Ignore. @@ -139,8 +204,7 @@ def replacetokens(tokens, opts): continue # String literal. Prefix to make a b'' string. - yield adjusttokenpos(t._replace(string='b%s' % t.string), - coloffset) + yield adjusttokenpos(t._replace(string='b%s' % t.string), coloffset) coldelta += 1 continue @@ -149,8 +213,15 @@ def replacetokens(tokens, opts): fn = t.string # *attr() builtins don't accept byte strings to 2nd argument. - if (fn in ('getattr', 'setattr', 'hasattr', 'safehasattr') and - not _isop(i - 1, '.')): + if fn in ( + 'getattr', + 'setattr', + 'hasattr', + 'safehasattr', + 'wrapfunction', + 'wrapclass', + 'addattr', + ) and (opts['allow-attr-methods'] or not _isop(i - 1, '.')): arg1idx = _findargnofcall(1) if arg1idx is not None: _ensuresysstr(arg1idx) @@ -169,19 +240,30 @@ def replacetokens(tokens, opts): yield adjusttokenpos(t._replace(string=fn[4:]), coloffset) continue + if t.type == token.NAME and t.string in opts['treat-as-kwargs']: + if _isitemaccess(i): + _ensuresysstr(i + 2) + if _ismethodcall(i, 'get', 'pop', 'setdefault', 'popitem'): + _ensuresysstr(i + 4) + # Looks like "if __name__ == '__main__'". - if (t.type == token.NAME and t.string == '__name__' - and _isop(i + 1, '==')): + if ( + t.type == token.NAME + and t.string == '__name__' + and _isop(i + 1, '==') + ): _ensuresysstr(i + 2) # Emit unmodified token. yield adjusttokenpos(t, coloffset) + def process(fin, fout, opts): tokens = tokenize.tokenize(fin.readline) tokens = replacetokens(list(tokens), opts) fout.write(tokenize.untokenize(tokens)) + def tryunlink(fname): try: os.unlink(fname) @@ -189,12 +271,14 @@ def tryunlink(fname): if err.errno != errno.ENOENT: raise + @contextlib.contextmanager def editinplace(fname): n = os.path.basename(fname) d = os.path.dirname(fname) - fp = tempfile.NamedTemporaryFile(prefix='.%s-' % n, suffix='~', dir=d, - delete=False) + fp = tempfile.NamedTemporaryFile( + prefix='.%s-' % n, suffix='~', dir=d, delete=False + ) try: yield fp fp.close() @@ -205,16 +289,43 @@ def editinplace(fname): fp.close() tryunlink(fp.name) + def main(): ap = argparse.ArgumentParser() - ap.add_argument('-i', '--inplace', action='store_true', default=False, - help='edit files in place') - ap.add_argument('--dictiter', action='store_true', default=False, - help='rewrite iteritems() and itervalues()'), + ap.add_argument( + '--version', action='version', version='Byteify strings 1.0' + ) + ap.add_argument( + '-i', + '--inplace', + action='store_true', + default=False, + help='edit files in place', + ) + ap.add_argument( + '--dictiter', + action='store_true', + default=False, + help='rewrite iteritems() and itervalues()', + ), + ap.add_argument( + '--allow-attr-methods', + action='store_true', + default=False, + help='also handle attr*() when they are methods', + ), + ap.add_argument( + '--treat-as-kwargs', + nargs="+", + default=[], + help="ignore kwargs-like objects", + ), ap.add_argument('files', metavar='FILE', nargs='+', help='source file') args = ap.parse_args() opts = { 'dictiter': args.dictiter, + 'treat-as-kwargs': set(args.treat_as_kwargs), + 'allow-attr-methods': args.allow_attr_methods, } for fname in args.files: if args.inplace: @@ -226,6 +337,7 @@ def main(): fout = sys.stdout.buffer process(fin, fout, opts) + if __name__ == '__main__': if sys.version_info.major < 3: print('This script must be run under Python 3.') diff --git a/contrib/casesmash.py b/contrib/casesmash.py --- a/contrib/casesmash.py +++ b/contrib/casesmash.py @@ -1,12 +1,12 @@ from __future__ import absolute_import import __builtin__ import os -from mercurial import ( - util, -) +from mercurial import util + def lowerwrap(scope, funcname): f = getattr(scope, funcname) + def wrap(fname, *args, **kwargs): d, base = os.path.split(fname) try: @@ -19,11 +19,14 @@ def lowerwrap(scope, funcname): if fn.lower() == base.lower(): return f(os.path.join(d, fn), *args, **kwargs) return f(fname, *args, **kwargs) + scope.__dict__[funcname] = wrap + def normcase(path): return path.lower() + os.path.normcase = normcase for f in 'file open'.split(): diff --git a/contrib/catapipe.py b/contrib/catapipe.py --- a/contrib/catapipe.py +++ b/contrib/catapipe.py @@ -53,15 +53,28 @@ import timeit # Python version and OS timer = timeit.default_timer + def main(): parser = argparse.ArgumentParser() - parser.add_argument('pipe', type=str, nargs=1, - help='Path of named pipe to create and listen on.') - parser.add_argument('output', default='trace.json', type=str, nargs='?', - help='Path of json file to create where the traces ' - 'will be stored.') - parser.add_argument('--debug', default=False, action='store_true', - help='Print useful debug messages') + parser.add_argument( + 'pipe', + type=str, + nargs=1, + help='Path of named pipe to create and listen on.', + ) + parser.add_argument( + 'output', + default='trace.json', + type=str, + nargs='?', + help='Path of json file to create where the traces ' 'will be stored.', + ) + parser.add_argument( + '--debug', + default=False, + action='store_true', + help='Print useful debug messages', + ) args = parser.parse_args() fn = args.pipe[0] os.mkfifo(fn) @@ -86,19 +99,23 @@ def main(): payload_args = {} pid = _threadmap[session] ts_micros = (now - start) * 1000000 - out.write(json.dumps( - { - "name": label, - "cat": "misc", - "ph": _TYPEMAP[verb], - "ts": ts_micros, - "pid": pid, - "tid": 1, - "args": payload_args, - })) + out.write( + json.dumps( + { + "name": label, + "cat": "misc", + "ph": _TYPEMAP[verb], + "ts": ts_micros, + "pid": pid, + "tid": 1, + "args": payload_args, + } + ) + ) out.write(',\n') finally: os.unlink(fn) + if __name__ == '__main__': main() diff --git a/contrib/check-code.py b/contrib/check-code.py --- a/contrib/check-code.py +++ b/contrib/check-code.py @@ -26,11 +26,15 @@ import optparse import os import re import sys + if sys.version_info[0] < 3: opentext = open else: + def opentext(f): return open(f, encoding='latin1') + + try: xrange except NameError: @@ -42,6 +46,7 @@ except ImportError: import testparseutil + def compilere(pat, multiline=False): if multiline: pat = '(?m)' + pat @@ -52,10 +57,22 @@ def compilere(pat, multiline=False): pass return re.compile(pat) + # check "rules depending on implementation of repquote()" in each # patterns (especially pypats), before changing around repquote() -_repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q', - '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'} +_repquotefixedmap = { + ' ': ' ', + '\n': '\n', + '.': 'p', + ':': 'q', + '%': '%', + '\\': 'b', + '*': 'A', + '+': 'P', + '-': 'M', +} + + def _repquoteencodechr(i): if i > 255: return 'u' @@ -67,13 +84,17 @@ def _repquoteencodechr(i): if c.isdigit(): return 'n' return 'o' + + _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256)) + def repquote(m): t = m.group('text') t = t.translate(_repquotett) return m.group('quote') + t + m.group('quote') + def reppython(m): comment = m.group('comment') if comment: @@ -81,87 +102,103 @@ def reppython(m): return "#" * l + comment[l:] return repquote(m) + def repcomment(m): return m.group(1) + "#" * len(m.group(2)) + def repccomment(m): t = re.sub(r"((?<=\n) )|\S", "x", m.group(2)) return m.group(1) + t + "*/" + def repcallspaces(m): t = re.sub(r"\n\s+", "\n", m.group(2)) return m.group(1) + t + def repinclude(m): return m.group(1) + "" + def rephere(m): t = re.sub(r"\S", "x", m.group(2)) return m.group(1) + t testpats = [ - [ - (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"), - (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), - (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), - (r'(?'"), - (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), - (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"), - (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"), - (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"), - (r'\$\(.*\)', "don't use $(expr), use `expr`"), - (r'rm -rf \*', "don't use naked rm -rf, target a directory"), - (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'), - (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', - "use egrep for extended grep syntax"), - (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"), - (r'(?\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), - (r'^stop\(\)', "don't use 'stop' as a shell function name"), - (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), - (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"), - (r'^alias\b.*=', "don't use alias, use a function"), - (r'if\s*!', "don't use '!' to negate exit status"), - (r'/dev/u?random', "don't use entropy, use /dev/zero"), - (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), - (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', - "put a backslash-escaped newline after sed 'i' command"), - (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"), - (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"), - (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"), - (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"), - (r'\butil\.Abort\b', "directly use error.Abort"), - (r'\|&', "don't use |&, use 2>&1"), - (r'\w = +\w', "only one space after = allowed"), - (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"), - (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"), - (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"), - (r'grep.* -[ABC]', "don't use grep's context flags"), - (r'find.*-printf', - "don't use 'find -printf', it doesn't exist on BSD find(1)"), - (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"), - ], - # warnings - [ - (r'^function', "don't use 'function', use old style"), - (r'^diff.*-\w*N', "don't use 'diff -N'"), - (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"), - (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), - (r'kill (`|\$\()', "don't use kill, use killdaemons.py") - ] + [ + (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"), + (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), + (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), + (r'(?'"), + (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), + (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"), + (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"), + (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"), + (r'rm -rf \*', "don't use naked rm -rf, target a directory"), + ( + r'\[[^\]]+==', + '[ foo == bar ] is a bashism, use [ foo = bar ] instead', + ), + ( + r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', + "use egrep for extended grep syntax", + ), + (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"), + (r'(?\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), + (r'^stop\(\)', "don't use 'stop' as a shell function name"), + (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), + (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"), + (r'^alias\b.*=', "don't use alias, use a function"), + (r'if\s*!', "don't use '!' to negate exit status"), + (r'/dev/u?random', "don't use entropy, use /dev/zero"), + (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), + ( + r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', + "put a backslash-escaped newline after sed 'i' command", + ), + (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"), + (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"), + (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"), + (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"), + (r'\butil\.Abort\b', "directly use error.Abort"), + (r'\|&', "don't use |&, use 2>&1"), + (r'\w = +\w', "only one space after = allowed"), + ( + r'\bsed\b.*[^\\]\\n', + "don't use 'sed ... \\n', use a \\ and a newline", + ), + (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"), + (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"), + (r'grep.* -[ABC]', "don't use grep's context flags"), + ( + r'find.*-printf', + "don't use 'find -printf', it doesn't exist on BSD find(1)", + ), + (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"), + ], + # warnings + [ + (r'^function', "don't use 'function', use old style"), + (r'^diff.*-\w*N', "don't use 'diff -N'"), + (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"), + (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), + (r'kill (`|\$\()', "don't use kill, use killdaemons.py"), + ], ] testfilters = [ @@ -171,45 +208,72 @@ testfilters = [ uprefix = r"^ \$ " utestpats = [ - [ - (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"), - (uprefix + r'.*\|\s*sed[^|>\n]*\n', - "use regex test output patterns instead of sed"), - (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), - (uprefix + r'.*(? for continued lines"), - (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite " - "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx - '# no-msys'), # in test-pull.t which is skipped on windows - (r'^ [^$>].*27\.0\.0\.1', - 'use $LOCALIP not an explicit loopback address'), - (r'^ (?![>$] ).*\$LOCALIP.*[^)]$', - 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'), - (r'^ (cat|find): .*: \$ENOENT\$', - 'use test -f to test for file existence'), - (r'^ diff -[^ -]*p', - "don't use (external) diff with -p for portability"), - (r' readlink ', 'use readlink.py instead of readlink'), - (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)', - "glob timezone field in diff output for portability"), - (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@', - "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"), - (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@', - "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"), - (r'^ @@ -[0-9]+ [+][0-9]+ @@', - "use '@@ -N* +N* @@ (glob)' style chunk header for portability"), - (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff' - r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$', - "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"), - ], - # warnings - [ - (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$', - "glob match with no glob string (?, *, /, and $LOCALIP)"), - ] + [ + (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"), + ( + uprefix + r'.*\|\s*sed[^|>\n]*\n', + "use regex test output patterns instead of sed", + ), + (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), + (uprefix + r'.*(? for continued lines"), + ( + uprefix + r'.*:\.\S*/', + "x:.y in a path does not work on msys, rewrite " + "as x://.y, or see `hg log -k msys` for alternatives", + r'-\S+:\.|' '# no-msys', # -Rxxx + ), # in test-pull.t which is skipped on windows + ( + r'^ [^$>].*27\.0\.0\.1', + 'use $LOCALIP not an explicit loopback address', + ), + ( + r'^ (?![>$] ).*\$LOCALIP.*[^)]$', + 'mark $LOCALIP output lines with (glob) to help tests in BSD jails', + ), + ( + r'^ (cat|find): .*: \$ENOENT\$', + 'use test -f to test for file existence', + ), + ( + r'^ diff -[^ -]*p', + "don't use (external) diff with -p for portability", + ), + (r' readlink ', 'use readlink.py instead of readlink'), + ( + r'^ [-+][-+][-+] .* [-+]0000 \(glob\)', + "glob timezone field in diff output for portability", + ), + ( + r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@', + "use '@@ -N* +N,n @@ (glob)' style chunk header for portability", + ), + ( + r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@', + "use '@@ -N,n +N* @@ (glob)' style chunk header for portability", + ), + ( + r'^ @@ -[0-9]+ [+][0-9]+ @@', + "use '@@ -N* +N* @@ (glob)' style chunk header for portability", + ), + ( + uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff' + r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$', + "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)", + ), + ], + # warnings + [ + ( + r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$', + "glob match with no glob string (?, *, /, and $LOCALIP)", + ), + ], ] # transform plain test rules to unified test's @@ -235,157 +299,214 @@ utestfilters = [ # common patterns to check *.py commonpypats = [ - [ - (r'\\$', 'Use () to wrap long lines in Python, not \\'), - (r'^\s*def\s*\w+\s*\(.*,\s*\(', - "tuple parameter unpacking not available in Python 3+"), - (r'lambda\s*\(.*,.*\)', - "tuple parameter unpacking not available in Python 3+"), - (r'(?\s', '<> operator is not available in Python 3+, use !='), - (r'^\s*\t', "don't use tabs"), - (r'\S;\s*\n', "semicolon"), - (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"), - (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"), - (r'(\w|\)),\w', "missing whitespace after ,"), - (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), - (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), - (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), - (( - # a line ending with a colon, potentially with trailing comments - r':([ \t]*#[^\n]*)?\n' - # one that is not a pass and not only a comment - r'(?P[ \t]+)[^#][^\n]+\n' - # more lines at the same indent level - r'((?P=indent)[^\n]+\n)*' - # a pass at the same indent level, which is bogus - r'(?P=indent)pass[ \t\n#]' - ), 'omit superfluous pass'), - (r'[^\n]\Z', "no trailing newline"), - (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), -# (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', -# "don't use underbars in identifiers"), - (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ', - "don't use camelcase in identifiers", r'#.*camelcase-required'), - (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', - "linebreak after :"), - (r'class\s[^( \n]+:', "old-style class, use class foo(object)", - r'#.*old-style'), - (r'class\s[^( \n]+\(\):', - "class foo() creates old style object, use class foo(object)", - r'#.*old-style'), - (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist - if k not in ('print', 'exec')), - "Python keyword is not a function"), - (r',]', "unneeded trailing ',' in list"), -# (r'class\s[A-Z][^\(]*\((?!Exception)', -# "don't capitalize non-exception classes"), -# (r'in range\(', "use xrange"), -# (r'^\s*print\s+', "avoid using print in core and extensions"), - (r'[\x80-\xff]', "non-ASCII character literal"), - (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"), - (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist), - "gratuitous whitespace after Python keyword"), - (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"), -# (r'\s\s=', "gratuitous whitespace before ="), - (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', - "missing whitespace around operator"), - (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', - "missing whitespace around operator"), - (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', - "missing whitespace around operator"), - (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', - "wrong whitespace around ="), - (r'\([^()]*( =[^=]|[^<>!=]= )', - "no whitespace around = for named parameters"), - (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', - "don't use old-style two-argument raise, use Exception(message)"), - (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), - (r' [=!]=\s+(True|False|None)', - "comparison with singleton, use 'is' or 'is not' instead"), - (r'^\s*(while|if) [01]:', - "use True/False for constant Boolean expression"), - (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'), - (r'(?:(?\s', '<> operator is not available in Python 3+, use !='), + (r'^\s*\t', "don't use tabs"), + (r'\S;\s*\n', "semicolon"), + (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"), + (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"), + (r'(\w|\)),\w', "missing whitespace after ,"), + (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), + (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), + ( + ( + # a line ending with a colon, potentially with trailing comments + r':([ \t]*#[^\n]*)?\n' + # one that is not a pass and not only a comment + r'(?P[ \t]+)[^#][^\n]+\n' + # more lines at the same indent level + r'((?P=indent)[^\n]+\n)*' + # a pass at the same indent level, which is bogus + r'(?P=indent)pass[ \t\n#]' + ), + 'omit superfluous pass', + ), + (r'[^\n]\Z', "no trailing newline"), + (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), + ( + r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ', + "don't use camelcase in identifiers", + r'#.*camelcase-required', + ), + ( + r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', + "linebreak after :", + ), + ( + r'class\s[^( \n]+:', + "old-style class, use class foo(object)", + r'#.*old-style', + ), + ( + r'class\s[^( \n]+\(\):', + "class foo() creates old style object, use class foo(object)", + r'#.*old-style', + ), + ( + r'\b(%s)\(' + % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')), + "Python keyword is not a function", + ), + # (r'class\s[A-Z][^\(]*\((?!Exception)', + # "don't capitalize non-exception classes"), + # (r'in range\(', "use xrange"), + # (r'^\s*print\s+', "avoid using print in core and extensions"), + (r'[\x80-\xff]', "non-ASCII character literal"), + (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"), + ( + r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', + "gratuitous whitespace in () or []", + ), + # (r'\s\s=', "gratuitous whitespace before ="), + ( + r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', + "missing whitespace around operator", + ), + ( + r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', + "missing whitespace around operator", + ), + ( + r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', + "missing whitespace around operator", + ), + (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="), + ( + r'\([^()]*( =[^=]|[^<>!=]= )', + "no whitespace around = for named parameters", + ), + ( + r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', + "don't use old-style two-argument raise, use Exception(message)", + ), + (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), + ( + r' [=!]=\s+(True|False|None)', + "comparison with singleton, use 'is' or 'is not' instead", + ), + ( + r'^\s*(while|if) [01]:', + "use True/False for constant Boolean expression", + ), + (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'), + ( + r'(?:(?\#.*?$)| + ( + r"""(?msx)(?P\#.*?$)| ((?P('''|\"\"\"|(?(([^\\]|\\.)*?)) - (?P=quote))""", reppython), + (?P=quote))""", + reppython, + ), ] # filters to convert normal *.py files -pyfilters = [ -] + commonpyfilters +pyfilters = [] + commonpyfilters # non-filter patterns pynfpats = [ [ - (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"), - (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"), - (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]', - "use pycompat.isdarwin"), + (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"), + (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"), + ( + r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]', + "use pycompat.isdarwin", + ), ], # warnings [], ] # filters to convert *.py for embedded ones in test script -embeddedpyfilters = [ -] + commonpyfilters +embeddedpyfilters = [] + commonpyfilters # extension non-filter patterns pyextnfpats = [ @@ -451,42 +576,40 @@ pyextnfpats = [ txtfilters = [] txtpats = [ - [ - (r'\s$', 'trailing whitespace'), - ('.. note::[ \n][^\n]', 'add two newlines after note::') - ], - [] + [ + (r'\s$', 'trailing whitespace'), + ('.. note::[ \n][^\n]', 'add two newlines after note::'), + ], + [], ] cpats = [ - [ - (r'//', "don't use //-style comments"), - (r'\S\t', "don't use tabs except for indent"), - (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), - (r'.{81}', "line too long"), - (r'(while|if|do|for)\(', "use space after while/if/do/for"), - (r'return\(', "return is not a function"), - (r' ;', "no space before ;"), - (r'[^;] \)', "no space before )"), - (r'[)][{]', "space between ) and {"), - (r'\w+\* \w+', "use int *foo, not int* foo"), - (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"), - (r'\w+ (\+\+|--)', "use foo++, not foo ++"), - (r'\w,\w', "missing whitespace after ,"), - (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), - (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), - (r'^#\s+\w', "use #foo, not # foo"), - (r'[^\n]\Z', "no trailing newline"), - (r'^\s*#import\b', "use only #include in standard C code"), - (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"), - (r'strcat\(', "don't use strcat"), - - # rules depending on implementation of repquote() - ], - # warnings - [ - # rules depending on implementation of repquote() - ] + [ + (r'//', "don't use //-style comments"), + (r'\S\t', "don't use tabs except for indent"), + (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), + (r'(while|if|do|for)\(', "use space after while/if/do/for"), + (r'return\(', "return is not a function"), + (r' ;', "no space before ;"), + (r'[^;] \)', "no space before )"), + (r'[)][{]', "space between ) and {"), + (r'\w+\* \w+', "use int *foo, not int* foo"), + (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"), + (r'\w+ (\+\+|--)', "use foo++, not foo ++"), + (r'\w,\w', "missing whitespace after ,"), + (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), + (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), + (r'^#\s+\w', "use #foo, not # foo"), + (r'[^\n]\Z', "no trailing newline"), + (r'^\s*#import\b', "use only #include in standard C code"), + (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"), + (r'strcat\(', "don't use strcat"), + # rules depending on implementation of repquote() + ], + # warnings + [ + # rules depending on implementation of repquote() + ], ] cfilters = [ @@ -497,82 +620,109 @@ cfilters = [ ] inutilpats = [ - [ - (r'\bui\.', "don't use ui in util"), - ], - # warnings - [] + [(r'\bui\.', "don't use ui in util"),], + # warnings + [], ] inrevlogpats = [ - [ - (r'\brepo\.', "don't use repo in revlog"), - ], - # warnings - [] + [(r'\brepo\.', "don't use repo in revlog"),], + # warnings + [], ] webtemplatefilters = [] webtemplatepats = [ - [], - [ - (r'{desc(\|(?!websub|firstline)[^\|]*)+}', - 'follow desc keyword with either firstline or websub'), - ] + [], + [ + ( + r'{desc(\|(?!websub|firstline)[^\|]*)+}', + 'follow desc keyword with either firstline or websub', + ), + ], ] allfilesfilters = [] allfilespats = [ - [ - (r'(http|https)://[a-zA-Z0-9./]*selenic.com/', - 'use mercurial-scm.org domain URL'), - (r'mercurial@selenic\.com', - 'use mercurial-scm.org domain for mercurial ML address'), - (r'mercurial-devel@selenic\.com', - 'use mercurial-scm.org domain for mercurial-devel ML address'), - ], - # warnings - [], + [ + ( + r'(http|https)://[a-zA-Z0-9./]*selenic.com/', + 'use mercurial-scm.org domain URL', + ), + ( + r'mercurial@selenic\.com', + 'use mercurial-scm.org domain for mercurial ML address', + ), + ( + r'mercurial-devel@selenic\.com', + 'use mercurial-scm.org domain for mercurial-devel ML address', + ), + ], + # warnings + [], ] py3pats = [ - [ - (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'), - (r'os\.name', "use pycompat.osname instead (py3)"), - (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'), - (r'os\.sep', "use pycompat.ossep instead (py3)"), - (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"), - (r'os\.altsep', "use pycompat.osaltsep instead (py3)"), - (r'sys\.platform', "use pycompat.sysplatform instead (py3)"), - (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"), - (r'os\.getenv', "use encoding.environ.get instead"), - (r'os\.setenv', "modifying the environ dict is not preferred"), - (r'(?|int|bool|list)\( # First argument. @@ -23,9 +24,12 @@ configre = re.compile(br''' # Second argument ['"](?P