text: # Copyright (c) Facebook, Inc. and its affiliates.
  #
  # This source code is licensed under the MIT license found in the
  # LICENSE file in the root directory of this source tree.
  
  import json
  import logging
  import os
  import platform
  import re
  import shutil
  import subprocess
  import tempfile
  from pathlib import Path
  from typing import List, Tuple, Optional
  
  # just validate that it's available, but we don't invoke it directly
  import wheel as _wheel  # noqa
  from twine.commands.check import check as twine_check
  
  from .setup import run as run_setup
  
  
  MODULE_NAME = "pyre_check"
  RUNTIME_DEPENDENCIES = [
      "async-generator",
      "click",
      "dataclasses",
      "dataclasses-json",
      "libcst>=0.3.6",
      "psutil",
      "pyre-extensions",
      "pywatchman",
      "tabulate",
      "typing-extensions",
  ]
  
  LOG: logging.Logger = logging.getLogger(__name__)
  
  
  def _distribution_platform() -> str:
      if platform.system() == "Linux":
          return "-manylinux1_x86_64"
      return "-macosx_10_11_x86_64"
  
  
  def _validate_typeshed(typeshed_path: Path) -> None:
      path = typeshed_path.absolute() / "stdlib"
      if not path.is_dir():
          raise ValueError(
              "The provided typeshed directory is not in the expected format: \
              It does not contain a 'stdlib' directory."
          )
  
  
  def _validate_version(version: str) -> None:
      pattern = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$")
      if not pattern.match(version):
          raise ValueError("Invalid version format.")
  
  
  def _mkdir_and_init(module_path: Path, version: Optional[str] = None) -> None:
      module_path.mkdir()
      init_path = module_path / "__init__.py"
      if version is None:
          init_path.touch()
      else:
          init_path.write_text(
              f"""\
  # Copyright (c) Facebook, Inc. and its affiliates.
  #
  # This source code is licensed under the MIT license found in the
  # LICENSE file in the root directory of this source tree.
  
  __version__ = "{version}"
  """
          )
  
  
  def _add_init_files(build_root: Path, version: str) -> None:
      # setup.py sdist will refuse to work for directories without a `__init__.py`.
      module_path = build_root / MODULE_NAME
      _mkdir_and_init(module_path, version)
      _mkdir_and_init(module_path / "tools")
      _mkdir_and_init(module_path / "tools/upgrade")
      _mkdir_and_init(module_path / "tools/upgrade/commands")
      _mkdir_and_init(module_path / "client")
  
  
  def _rsync_files(
      filters: List[str],
      source_directory: Path,
      target_directory: Path,
      arguments: List[str],
  ) -> None:
      command = ["rsync"]
      command.extend(arguments)
      command.extend(["--filter=" + filter_string for filter_string in filters])
      command.append(str(source_directory))
      command.append(str(target_directory))
      subprocess.run(command)
  
  
  def _sync_python_files(pyre_directory: Path, build_root: Path) -> None:
      target_root = build_root / MODULE_NAME
      filters = ["- tests/", "+ */", "-! *.py"]
      _rsync_files(filters, pyre_directory / "client", target_root, ["-avm"])
      _rsync_files(
          filters, pyre_directory / "tools" / "upgrade", target_root / "tools", ["-avm"]
      )
  
  
  def _sync_pysa_stubs(pyre_directory: Path, build_root: Path) -> None:
      filters = ["+ */"]
      _rsync_files(filters, pyre_directory / "stubs" / "taint", build_root, ["-avm"])
      _rsync_files(
          filters, pyre_directory / "stubs" / "third_party_taint", build_root, ["-avm"]
      )
  
  
  def _sync_stubs(pyre_directory: Path, build_root: Path) -> None:
      _rsync_files(
          [],
          pyre_directory / "stubs",
          build_root,
          [
              "--recursive",
              "--copy-links",
              "--prune-empty-dirs",
              "--verbose",
              "--include='django/***'",
              "--include='lxml/***'",
              "--exclude='*'",
          ],
      )
  
  
  def _sync_typeshed(build_root: Path, typeshed_path: Path) -> None:
      typeshed_target = build_root / "typeshed"
      _rsync_files(
          ["+ */", "-! *.pyi"], typeshed_path / "stdlib", typeshed_target, ["-avm"]
      )
      _rsync_files(
          ["+ */", "-! *.pyi"], typeshed_path / "third_party", typeshed_target, ["-avm"]
      )
      _rsync_files(
          [],
          typeshed_target,
          build_root,
          [
              "--recursive",
              "--copy-links",
              "--prune-empty-dirs",
              "--verbose",
              "--chmod='+w'",
              "--include='stdlib/***'",
              "--include='third_party/***'",
              "--exclude='*'",
          ],
      )
  
  
  def _patch_version(version: str, build_root: Path) -> None:
      file_contents = "__version__ = {}".format(version)
      (build_root / MODULE_NAME / "client/version.py").write_text(file_contents)
  
  
  def _binary_exists(pyre_directory: Path) -> bool:
      return (pyre_directory / "source" / "_build/default/main.exe").is_file()
  
  
  def _sync_binary(pyre_directory: Path, build_root: Path) -> None:
      (build_root / "bin").mkdir()
      shutil.copy(
          pyre_directory / "source" / "_build/default/main.exe",
          build_root / "bin/pyre.bin",
      )
  
  
  def _sync_documentation_files(pyre_directory: Path, build_root: Path) -> None:
      shutil.copy(pyre_directory / "README.md", build_root)
      shutil.copy(pyre_directory / "LICENSE", build_root)
  
  
  def _create_setup_configuration(build_root: Path) -> None:
      setup_cfg = build_root / "setup.cfg"
      setup_cfg.touch()
      setup_cfg.write_text("[metadata]\nlicense_file = LICENSE")
  
  
  def _create_setup_py(
      pyre_directory: Path, version: str, build_root: Path, nightly: bool
  ) -> None:
      path = pyre_directory / "scripts/pypi/setup.py"
      setup_template = path.read_text()
      runtime_dependencies = json.dumps(RUNTIME_DEPENDENCIES)
      setup_contents = setup_template.format(
          PACKAGE_NAME="pyre-check-nightly" if nightly else "pyre-check",
          PACKAGE_VERSION=version,
          MODULE_NAME=MODULE_NAME,
          RUNTIME_DEPENDENCIES=runtime_dependencies,
      )
  
      (build_root / "setup.py").write_text(setup_contents)
  
  
  def _run_setup_command(
      pyre_directory: Path,
      build_root: Path,
      version: str,
      command: str,
      nightly: bool,
  ) -> None:
      with open(pyre_directory / "README.md") as f:
          long_description = f.read()
      old_dir = os.getcwd()
      os.chdir(build_root)
      run_setup(
          package_name="pyre-check-nightly" if nightly else "pyre-check",
          package_version=version,
          module_name=MODULE_NAME,
          runtime_dependencies=RUNTIME_DEPENDENCIES,
          long_description=long_description,
          script_name="setup.py",
          script_args=[command],
      )
      os.chdir(old_dir)
  
  
  def _create_dist_directory(pyre_directory: Path) -> None:
      (pyre_directory / "scripts" / "dist").mkdir(exist_ok=True)
  
  
  def _rename_and_move_artifacts(
      pyre_directory: Path, build_root: Path
  ) -> Tuple[Path, Path]:
      dist_directory = build_root / "dist"
      wheel = list(dist_directory.glob("**/*.whl"))
      source_distribution = list(dist_directory.glob("**/*.tar.gz"))
      # make sure the appropriate numbers of files are present in the dist folder
      if not len(wheel) == 1 and not len(source_distribution) == 1:
          raise ValueError("Unexpected files found in {}/dist.".format(build_root))
      source_distribution, wheel = source_distribution[0], wheel[0]
      destination_path = pyre_directory / "scripts" / "dist"
      source_distribution_name = source_distribution.name
      source_distribution_destination = destination_path / (
          source_distribution_name.split(".tar.gz")[0]
          + _distribution_platform()
          + ".tar.gz"
      )
      wheel_name = wheel.name
      wheel_destination = destination_path / wheel_name.replace(
          "-any", _distribution_platform()
      )
      shutil.move(wheel, wheel_destination)
      shutil.move(source_distribution, source_distribution_destination)
      return wheel_destination, source_distribution_destination
  
  
  def build_pypi_package(
      pyre_directory: Path, typeshed_path: Path, version: str, nightly: bool
  ) -> None:
      _validate_typeshed(typeshed_path)
      _validate_version(version)
      if not _binary_exists(pyre_directory):
          raise ValueError(
              "The binary file does not exist. \
              Have you run 'make' in the toplevel directory?"
          )
  
      with tempfile.TemporaryDirectory() as build_root:
          build_path = Path(build_root)
          _add_init_files(build_path, version)
          _patch_version(version, build_path)
          _create_setup_py(pyre_directory, version, build_path, nightly)
  
          _sync_python_files(pyre_directory, build_path)
          _sync_pysa_stubs(pyre_directory, build_path)
          _sync_stubs(pyre_directory, build_path)
          _sync_typeshed(build_path, typeshed_path)
          _sync_binary(pyre_directory, build_path)
          _sync_documentation_files(pyre_directory, build_path)
          _sync_stubs(pyre_directory, build_path)
  
          _run_setup_command(pyre_directory, build_root, version, "sdist", nightly)
          _create_dist_directory(pyre_directory)
          _create_setup_configuration(build_path)
          twine_check([path.as_posix() for path in (build_path / "dist").iterdir()])
  
          _run_setup_command(pyre_directory, build_root, version, "bdist_wheel", nightly)
          wheel_destination, distribution_destination = _rename_and_move_artifacts(
              pyre_directory, build_path
          )
          LOG.info("All done.")
          LOG.info("\n Build artifact available at:\n {}\n".format(wheel_destination))
          LOG.info(
              "\n Source distribution available at:\n {}\n".format(
                  distribution_destination
              )
          )
  