simplify ci build script, add MSIX installer and Microsoft store.
This commit is contained in:
parent
55a64b7ad9
commit
3872d33111
|
@ -92,7 +92,7 @@ jobs:
|
|||
# install dependencies (requires internet connectivity)
|
||||
tox -e py --notest
|
||||
# run tests with loopback only. We need to sudo for unshare, which means we need an absolute path for tox.
|
||||
sudo unshare --net -- sh -c "ip link set lo up; $(which tox) -e py"
|
||||
sudo unshare --net -- sh -c "ip link set lo up; $(which tox) -e py"
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- uses: codecov/codecov-action@a1ed4b322b4b38cb846afb5a0ebfa17086917d27
|
||||
# mirrored below and at https://github.com/mitmproxy/mitmproxy/settings/actions
|
||||
|
@ -113,9 +113,6 @@ jobs:
|
|||
platform: linux
|
||||
runs-on: ${{ matrix.image }}
|
||||
env:
|
||||
CI_BUILD_WHEEL: ${{ matrix.platform == 'linux' }}
|
||||
CI_BUILD_PYINSTALLER: 1
|
||||
CI_BUILD_WININSTALLER: ${{ matrix.platform == 'windows' }}
|
||||
CI_BUILD_KEY: ${{ secrets.CI_BUILD_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
@ -131,12 +128,24 @@ jobs:
|
|||
path: release/installbuilder/setup
|
||||
key: installbuilder
|
||||
- run: pip install -e .[dev]
|
||||
- run: python release/cibuild.py build
|
||||
# artifacts must have different names, see https://github.com/actions/upload-artifact/issues/24
|
||||
- uses: actions/upload-artifact@v2
|
||||
- if: matrix.platform == 'linux'
|
||||
run: python -u release/build.py standalone-binaries wheel
|
||||
- if: matrix.platform == 'windows'
|
||||
run: python -u release/build.py standalone-binaries installbuilder-installer msix-installer
|
||||
- if: matrix.platform == 'macos'
|
||||
run: python -u release/build.py standalone-binaries
|
||||
- if: matrix.platform == 'windows' # separate artifact because we don't want it on the snapshot server.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: msix-installer
|
||||
path: release/dist/*.msix
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# artifacts must have different names, see https://github.com/actions/upload-artifact/issues/24
|
||||
name: binaries.${{ matrix.platform }}
|
||||
path: release/dist
|
||||
path: |
|
||||
release/dist
|
||||
!release/dist/*.msix
|
||||
|
||||
test-web-ui:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -182,7 +191,7 @@ jobs:
|
|||
sudo dpkg -i hugo*.deb
|
||||
- run: pip install -e .[dev]
|
||||
- run: ./docs/build.py
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
path: docs/public
|
||||
|
@ -190,9 +199,9 @@ jobs:
|
|||
# Separate from everything else because slow.
|
||||
build-and-deploy-docker:
|
||||
if: github.repository == 'mitmproxy/mitmproxy' && (
|
||||
github.ref == 'refs/heads/main' ||
|
||||
github.ref == 'refs/heads/dockertest' ||
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
github.ref == 'refs/heads/main'
|
||||
|| github.ref == 'refs/heads/citest'
|
||||
|| startsWith(github.ref, 'refs/tags/')
|
||||
)
|
||||
environment: deploy-docker
|
||||
needs:
|
||||
|
@ -202,7 +211,6 @@ jobs:
|
|||
- docs
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_BUILD_DOCKER: 1
|
||||
DOCKER_USERNAME: mitmbot
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
steps:
|
||||
|
@ -218,9 +226,7 @@ jobs:
|
|||
path: release/dist
|
||||
- uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480 # v1.2.0
|
||||
- uses: docker/setup-buildx-action@b1f1f719c7cd5364be7c82e366366da322d01f7c # v1.6.0
|
||||
- run: pip install -e .[dev]
|
||||
- run: python release/cibuild.py build
|
||||
- run: python release/cibuild.py upload
|
||||
- run: python release/build-and-deploy-docker.py
|
||||
|
||||
deploy:
|
||||
# This action has access to our AWS keys, so we are extra careful here.
|
||||
|
@ -239,6 +245,10 @@ jobs:
|
|||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-west-2
|
||||
MSFT_APP_ID: 9NWNDLQMNZD7
|
||||
MSFT_TENANT_ID: ${{ secrets.MSFT_TENANT_ID }}
|
||||
MSFT_CLIENT_ID: ${{ secrets.MSFT_CLIENT_ID }}
|
||||
MSFT_CLIENT_SECRET: ${{ secrets.MSFT_CLIENT_SECRET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
@ -248,13 +258,34 @@ jobs:
|
|||
python-version: '3.10'
|
||||
- run: sudo apt-get update
|
||||
- run: sudo apt-get install -y twine awscli
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
path: docs/public
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries.windows
|
||||
path: release/dist
|
||||
- run: mv release/dist/docs docs/public
|
||||
# move artifacts from their subfolders into release/dist
|
||||
- run: find release/dist -mindepth 2 -type f -exec mv {} release/dist \;
|
||||
# and then delete the empty folders
|
||||
- run: find release/dist -type d -empty -delete
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries.linux
|
||||
path: release/dist
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries.macos
|
||||
path: release/dist
|
||||
- run: ls docs/public
|
||||
- run: ls release/dist
|
||||
- run: ./release/deploy.py
|
||||
# We don't want the MSIX installer on the snapshot server, so we get it only now.
|
||||
- if: github.ref == 'refs/heads/citest' || startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: msix-installer
|
||||
path: release/dist/
|
||||
- if: github.ref == 'refs/heads/citest'
|
||||
run: ./release/deploy-microsoft-store.py release/dist/*.msix
|
||||
env:
|
||||
MSFT_APP_FLIGHT: 174ca570-8cae-4444-9858-c07293f1f13a
|
||||
- if: startsWith(github.ref, 'refs/tags/')
|
||||
run: ./release/deploy-microsoft-store.py release/dist/*.msix
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
# Release Checklist
|
||||
|
||||
These steps assume you are on the correct branch and have a git remote called `origin` that points to the `mitmproxy/mitmproxy` repo. If necessary, create a major version branch starting off the release tag (e.g. `git checkout -b v4.x v4.0.0`) first.
|
||||
These steps assume you are on the correct branch and have a git remote called `origin` that points to the
|
||||
`mitmproxy/mitmproxy` repo. If necessary, create a major version branch starting off the release tag
|
||||
(e.g. `git checkout -b v4.x 4.0.0`) first.
|
||||
|
||||
- Update CHANGELOG.
|
||||
- Verify that the compiled mitmweb assets are up-to-date (`npm start prod`).
|
||||
- Verify that all CI tests pass.
|
||||
- Verify that `mitmproxy/version.py` is correct. Remove `.dev` suffix if it exists.
|
||||
- Tag the release and push to GitHub.
|
||||
- `git tag v4.0.0`
|
||||
- `git push origin v4.0.0`
|
||||
- `git tag 4.0.0`
|
||||
- `git push origin 4.0.0`
|
||||
- Wait for tag CI to complete.
|
||||
|
||||
### GitHub Releases
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Building and deploying docker images is a bit of a special snowflake as we don't get a file we can upload/download
|
||||
as an artifact. So we need to do everything in one job.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Security: No third-party dependencies here!
|
||||
|
||||
root = Path(__file__).absolute().parent.parent
|
||||
|
||||
ref = os.environ["GITHUB_REF"]
|
||||
branch: Optional[str] = None
|
||||
tag: Optional[str] = None
|
||||
if ref.startswith("refs/heads/"):
|
||||
branch = ref.replace("refs/heads/", "")
|
||||
elif ref.startswith("refs/tags/"):
|
||||
tag = ref.replace("refs/tags/", "")
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
(whl,) = root.glob("release/dist/mitmproxy-*-py3-none-any.whl")
|
||||
docker_build_dir = root / "release/docker"
|
||||
shutil.copy(whl, docker_build_dir / whl.name)
|
||||
|
||||
# Build for this platform and test if it runs.
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
"localtesting",
|
||||
"--load",
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
r = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"localtesting",
|
||||
"mitmdump",
|
||||
"--version",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
print(r.stdout.decode())
|
||||
assert "Mitmproxy: " in r.stdout.decode()
|
||||
|
||||
# Now we can deploy.
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"login",
|
||||
"-u",
|
||||
os.environ["DOCKER_USERNAME"],
|
||||
"-p",
|
||||
os.environ["DOCKER_PASSWORD"],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _buildx(docker_tag):
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
docker_tag,
|
||||
"--push",
|
||||
"--platform",
|
||||
"linux/amd64,linux/arm64",
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
|
||||
|
||||
if branch == "main":
|
||||
_buildx("mitmproxy/mitmproxy:dev")
|
||||
elif branch == "citest":
|
||||
_buildx("mitmproxy/mitmproxy:citest")
|
||||
elif tag:
|
||||
_buildx(f"mitmproxy/mitmproxy:{tag}")
|
||||
_buildx("mitmproxy/mitmproxy:latest")
|
||||
else:
|
||||
raise AssertionError
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import click
|
||||
import cryptography.fernet
|
||||
|
||||
here = Path(__file__).absolute().parent
|
||||
|
||||
TEMP_DIR = here / "build"
|
||||
DIST_DIR = here / "dist"
|
||||
|
||||
|
||||
@click.group(chain=True)
|
||||
@click.option("--dirty", is_flag=True)
|
||||
def cli(dirty):
|
||||
if dirty:
|
||||
print("Keeping temporary files.")
|
||||
else:
|
||||
print("Cleaning up temporary files...")
|
||||
if TEMP_DIR.exists():
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
if DIST_DIR.exists():
|
||||
shutil.rmtree(DIST_DIR)
|
||||
|
||||
TEMP_DIR.mkdir()
|
||||
DIST_DIR.mkdir()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def wheel():
|
||||
"""Build the wheel for PyPI."""
|
||||
print("Building wheel...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"python",
|
||||
"setup.py",
|
||||
"-q",
|
||||
"bdist_wheel",
|
||||
"--dist-dir",
|
||||
DIST_DIR,
|
||||
]
|
||||
)
|
||||
(whl,) = DIST_DIR.glob("mitmproxy-*-py3-none-any.whl")
|
||||
print(f"Found wheel package: {whl}")
|
||||
subprocess.check_call(["tox", "-e", "wheeltest", "--", whl])
|
||||
|
||||
|
||||
class ZipFile2(zipfile.ZipFile):
|
||||
# ZipFile and tarfile have slightly different APIs. Let's fix that.
|
||||
def add(self, name: str, arcname: str) -> None:
|
||||
return self.write(name, arcname)
|
||||
|
||||
def __enter__(self) -> ZipFile2:
|
||||
return self
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
assert self.filename
|
||||
return self.filename
|
||||
|
||||
|
||||
def archive(path: Path) -> tarfile.TarFile | ZipFile2:
|
||||
if platform.system() == "Windows":
|
||||
return ZipFile2(path.with_suffix(".zip"), "w")
|
||||
else:
|
||||
return tarfile.open(path.with_suffix(".tar.gz"), "w:gz")
|
||||
|
||||
|
||||
def version() -> str:
|
||||
if ref := os.environ.get("GITHUB_REF", ""):
|
||||
if ref.startswith("refs/heads/"):
|
||||
return ref.replace("refs/heads/", "")
|
||||
if ref.startswith("refs/tags/"):
|
||||
return ref.replace("refs/tags/", "")
|
||||
return os.environ.get("BUILD_VERSION", "dev")
|
||||
|
||||
|
||||
def operating_system() -> Literal["windows", "linux", "macos", "unknown"]:
|
||||
match platform.system():
|
||||
case "Windows":
|
||||
return "windows"
|
||||
case "Linux":
|
||||
return "linux"
|
||||
case "Darwin":
|
||||
return "macos"
|
||||
case _:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _pyinstaller(specfile: str) -> None:
|
||||
print(f"Invoking PyInstaller with {specfile}...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"pyinstaller",
|
||||
"--clean",
|
||||
"--workpath",
|
||||
TEMP_DIR / "pyinstaller/temp",
|
||||
"--distpath",
|
||||
TEMP_DIR / "pyinstaller/dist",
|
||||
specfile,
|
||||
],
|
||||
cwd=here / "specs",
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def standalone_binaries():
|
||||
"""All platforms: Build the standalone binaries generated with PyInstaller"""
|
||||
with archive(DIST_DIR / f"mitmproxy-{version()}-{operating_system()}") as f:
|
||||
_pyinstaller("standalone.spec")
|
||||
|
||||
for tool in ["mitmproxy", "mitmdump", "mitmweb"]:
|
||||
executable = TEMP_DIR / "pyinstaller/dist" / tool
|
||||
if platform.system() == "Windows":
|
||||
executable = executable.with_suffix(".exe")
|
||||
|
||||
# Test if it works at all O:-)
|
||||
print(f"> {executable} --version")
|
||||
subprocess.check_call([executable, "--version"])
|
||||
|
||||
f.add(str(executable), str(executable.name))
|
||||
print(f"Packed {f.name}.")
|
||||
|
||||
|
||||
def _ensure_pyinstaller_onedir():
|
||||
if not (TEMP_DIR / "pyinstaller/dist/onedir").exists():
|
||||
_pyinstaller("windows-dir.spec")
|
||||
|
||||
for tool in ["mitmproxy", "mitmdump", "mitmweb"]:
|
||||
print(f"> {tool} --version")
|
||||
executable = (TEMP_DIR / "pyinstaller/dist/onedir" / tool).with_suffix(".exe")
|
||||
subprocess.check_call([executable, "--version"])
|
||||
|
||||
|
||||
@cli.command()
|
||||
def msix_installer():
|
||||
"""Windows: Build the MSIX installer for the Windows Store."""
|
||||
_ensure_pyinstaller_onedir()
|
||||
|
||||
shutil.copytree(
|
||||
TEMP_DIR / "pyinstaller/dist/onedir",
|
||||
TEMP_DIR / "msix",
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
shutil.copytree(here / "windows-installer", TEMP_DIR / "msix", dirs_exist_ok=True)
|
||||
|
||||
manifest = TEMP_DIR / "msix/AppxManifest.xml"
|
||||
app_version = version()
|
||||
if app_version in ["citest", "dev"]:
|
||||
app_version = datetime.now().strftime("%y%m.%d.%H%M").replace(".0", ".").replace(".0", ".").replace(".0", ".")
|
||||
manifest.write_text(manifest.read_text().replace("1.2.3", app_version))
|
||||
|
||||
makeappx_exe = (
|
||||
Path(os.environ["ProgramFiles(x86)"])
|
||||
/ "Windows Kits/10/App Certification Kit/makeappx.exe"
|
||||
)
|
||||
subprocess.check_call(
|
||||
[
|
||||
makeappx_exe,
|
||||
"pack",
|
||||
"/d",
|
||||
TEMP_DIR / "msix",
|
||||
"/p",
|
||||
DIST_DIR / f"mitmproxy-{version()}-installer.msix",
|
||||
],
|
||||
)
|
||||
assert (DIST_DIR / f"mitmproxy-{version()}-installer.msix").exists()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def installbuilder_installer():
|
||||
"""Windows: Build the InstallBuilder installer."""
|
||||
_ensure_pyinstaller_onedir()
|
||||
|
||||
IB_VERSION = "21.6.0"
|
||||
IB_SETUP_SHA256 = "2bc9f9945cb727ad176aa31fa2fa5a8c57a975bad879c169b93e312af9d05814"
|
||||
IB_DIR = here / "installbuilder"
|
||||
IB_SETUP = IB_DIR / "setup" / f"{IB_VERSION}-installer.exe"
|
||||
IB_CLI = Path(
|
||||
rf"C:\Program Files\VMware InstallBuilder Enterprise {IB_VERSION}\bin\builder-cli.exe"
|
||||
)
|
||||
IB_LICENSE = IB_DIR / "license.xml"
|
||||
|
||||
if not IB_LICENSE.exists():
|
||||
print("Decrypt InstallBuilder license...")
|
||||
f = cryptography.fernet.Fernet(os.environ["CI_BUILD_KEY"].encode())
|
||||
with open(IB_LICENSE.with_suffix(".xml.enc"), "rb") as infile, open(
|
||||
IB_LICENSE, "wb"
|
||||
) as outfile:
|
||||
outfile.write(f.decrypt(infile.read()))
|
||||
|
||||
if not IB_CLI.exists():
|
||||
if not IB_SETUP.exists():
|
||||
print("Downloading InstallBuilder...")
|
||||
|
||||
def report(block, blocksize, total):
|
||||
done = block * blocksize
|
||||
if round(100 * done / total) != round(100 * (done - blocksize) / total):
|
||||
print(f"Downloading... {round(100 * done / total)}%")
|
||||
|
||||
tmp = IB_SETUP.with_suffix(".tmp")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://clients.bitrock.com/installbuilder/installbuilder-enterprise-{IB_VERSION}-windows-x64-installer.exe",
|
||||
tmp,
|
||||
reporthook=report,
|
||||
)
|
||||
tmp.rename(IB_SETUP)
|
||||
|
||||
ib_setup_hash = hashlib.sha256()
|
||||
with IB_SETUP.open("rb") as fp:
|
||||
while True:
|
||||
data = fp.read(65_536)
|
||||
if not data:
|
||||
break
|
||||
ib_setup_hash.update(data)
|
||||
if ib_setup_hash.hexdigest() != IB_SETUP_SHA256: # pragma: no cover
|
||||
raise RuntimeError("InstallBuilder hashes don't match.")
|
||||
|
||||
print("Install InstallBuilder...")
|
||||
subprocess.run(
|
||||
[IB_SETUP, "--mode", "unattended", "--unattendedmodeui", "none"], check=True
|
||||
)
|
||||
assert IB_CLI.is_file()
|
||||
|
||||
print("Run InstallBuilder...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
IB_CLI,
|
||||
"build",
|
||||
str(IB_DIR / "mitmproxy.xml"),
|
||||
"windows-x64",
|
||||
"--license",
|
||||
str(IB_LICENSE),
|
||||
"--setvars",
|
||||
f"project.version={version()}",
|
||||
"--verbose",
|
||||
]
|
||||
)
|
||||
assert (DIST_DIR / f"mitmproxy-{version()}-windows-x64-installer.exe").exists()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
|
@ -1,633 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
import click
|
||||
import cryptography.fernet
|
||||
import parver
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def chdir(path: Path): # pragma: no cover
|
||||
old_dir = os.getcwd()
|
||||
os.chdir(path)
|
||||
yield
|
||||
os.chdir(old_dir)
|
||||
|
||||
|
||||
class BuildError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def bool_from_env(envvar: str) -> bool:
|
||||
val = os.environ.get(envvar, "")
|
||||
if not val or val.lower() in ("0", "false"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class ZipFile2(zipfile.ZipFile):
|
||||
# ZipFile and tarfile have slightly different APIs. Let's fix that.
|
||||
def add(self, name: str, arcname: str) -> None:
|
||||
return self.write(name, arcname)
|
||||
|
||||
def __enter__(self) -> "ZipFile2":
|
||||
return self
|
||||
|
||||
|
||||
@dataclass(frozen=True, repr=False)
|
||||
class BuildEnviron:
|
||||
PLATFORM_TAGS = {
|
||||
"Darwin": "osx",
|
||||
"Windows": "windows",
|
||||
"Linux": "linux",
|
||||
}
|
||||
|
||||
system: str
|
||||
root_dir: Path
|
||||
branch: Optional[str] = None
|
||||
tag: Optional[str] = None
|
||||
is_pull_request: bool = True
|
||||
should_build_wheel: bool = False
|
||||
should_build_docker: bool = False
|
||||
should_build_pyinstaller: bool = False
|
||||
should_build_wininstaller: bool = False
|
||||
has_aws_creds: bool = False
|
||||
has_twine_creds: bool = False
|
||||
docker_username: Optional[str] = None
|
||||
docker_password: Optional[str] = None
|
||||
build_key: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "BuildEnviron":
|
||||
branch = None
|
||||
tag = None
|
||||
|
||||
if ref := os.environ.get("GITHUB_REF", ""):
|
||||
if ref.startswith("refs/heads/"):
|
||||
branch = ref.replace("refs/heads/", "")
|
||||
if ref.startswith("refs/pull/"):
|
||||
branch = "pr-" + ref.split("/")[2]
|
||||
if ref.startswith("refs/tags/"):
|
||||
tag = ref.replace("refs/tags/", "")
|
||||
|
||||
is_pull_request = (
|
||||
os.environ.get("GITHUB_EVENT_NAME", "pull_request") == "pull_request"
|
||||
)
|
||||
|
||||
return cls(
|
||||
system=platform.system(),
|
||||
root_dir=Path(__file__).parent.parent,
|
||||
branch=branch,
|
||||
tag=tag,
|
||||
is_pull_request=is_pull_request,
|
||||
should_build_wheel=bool_from_env("CI_BUILD_WHEEL"),
|
||||
should_build_pyinstaller=bool_from_env("CI_BUILD_PYINSTALLER"),
|
||||
should_build_wininstaller=bool_from_env("CI_BUILD_WININSTALLER"),
|
||||
should_build_docker=bool_from_env("CI_BUILD_DOCKER"),
|
||||
has_aws_creds=bool_from_env("AWS_ACCESS_KEY_ID"),
|
||||
has_twine_creds=bool_from_env("TWINE_USERNAME")
|
||||
and bool_from_env("TWINE_PASSWORD"),
|
||||
docker_username=os.environ.get("DOCKER_USERNAME", None),
|
||||
docker_password=os.environ.get("DOCKER_PASSWORD", None),
|
||||
build_key=os.environ.get("CI_BUILD_KEY", None),
|
||||
)
|
||||
|
||||
def archive(self, path: Path) -> Union[tarfile.TarFile, ZipFile2]:
|
||||
if self.system == "Windows":
|
||||
return ZipFile2(path, "w")
|
||||
else:
|
||||
return tarfile.open(path, "w:gz")
|
||||
|
||||
@property
|
||||
def archive_path(self) -> Path:
|
||||
if self.system == "Windows":
|
||||
ext = "zip"
|
||||
else:
|
||||
ext = "tar.gz"
|
||||
return self.dist_dir / f"mitmproxy-{self.version}-{self.platform_tag}.{ext}"
|
||||
|
||||
@property
|
||||
def build_dir(self) -> Path:
|
||||
return self.release_dir / "build"
|
||||
|
||||
@property
|
||||
def dist_dir(self) -> Path:
|
||||
return self.release_dir / "dist"
|
||||
|
||||
@property
|
||||
def docker_tag(self) -> str:
|
||||
if self.branch == "main":
|
||||
t = "dev"
|
||||
else:
|
||||
t = self.version
|
||||
return f"mitmproxy/mitmproxy:{t}"
|
||||
|
||||
def dump_info(self, fp=sys.stdout) -> None:
|
||||
lst = [
|
||||
"version",
|
||||
"tag",
|
||||
"branch",
|
||||
"platform_tag",
|
||||
"root_dir",
|
||||
"release_dir",
|
||||
"build_dir",
|
||||
"dist_dir",
|
||||
"upload_dir",
|
||||
"should_build_wheel",
|
||||
"should_build_pyinstaller",
|
||||
"should_build_wininstaller",
|
||||
"should_build_docker",
|
||||
"should_upload_aws",
|
||||
"should_upload_docker",
|
||||
"should_upload_pypi",
|
||||
]
|
||||
for attr in lst:
|
||||
print(f"cibuild.{attr}={getattr(self, attr)}", file=fp)
|
||||
|
||||
def check_version(self) -> None:
|
||||
"""
|
||||
Check that version numbers match our conventions.
|
||||
Raises a ValueError if there is a mismatch.
|
||||
"""
|
||||
contents = (self.root_dir / "mitmproxy" / "version.py").read_text("utf8")
|
||||
match = re.search(r'^VERSION = "(.+?)"', contents, re.M)
|
||||
assert match
|
||||
version = match.group(1)
|
||||
|
||||
if self.is_prod_release:
|
||||
# For production releases, we require strict version equality
|
||||
if self.version != version:
|
||||
raise ValueError(
|
||||
f"Tag is {self.tag}, but mitmproxy/version.py is {version}."
|
||||
)
|
||||
elif not self.is_maintenance_branch:
|
||||
# Commits on maintenance branches don't need the dev suffix. This
|
||||
# allows us to incorporate and test commits between tagged releases.
|
||||
# For snapshots, we only ensure that mitmproxy/version.py contains a
|
||||
# dev release.
|
||||
version_info = parver.Version.parse(version)
|
||||
if not version_info.is_devrelease:
|
||||
raise ValueError(
|
||||
f"Non-production releases must have dev suffix: {version}"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_maintenance_branch(self) -> bool:
|
||||
"""
|
||||
Is this an untagged commit on a maintenance branch?
|
||||
"""
|
||||
if not self.tag and self.branch and re.match(r"v\d+\.x", self.branch):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def has_docker_creds(self) -> bool:
|
||||
return bool(self.docker_username and self.docker_password)
|
||||
|
||||
@property
|
||||
def is_prod_release(self) -> bool:
|
||||
if not self.tag or not self.tag.startswith("v"):
|
||||
return False
|
||||
try:
|
||||
v = parver.Version.parse(self.version, strict=True)
|
||||
except (parver.ParseError, BuildError):
|
||||
return False
|
||||
return not v.is_prerelease
|
||||
|
||||
@property
|
||||
def platform_tag(self) -> str:
|
||||
if self.system in self.PLATFORM_TAGS:
|
||||
return self.PLATFORM_TAGS[self.system]
|
||||
raise BuildError(f"Unsupported platform: {self.system}")
|
||||
|
||||
@property
|
||||
def release_dir(self) -> Path:
|
||||
return self.root_dir / "release"
|
||||
|
||||
@property
|
||||
def should_upload_docker(self) -> bool:
|
||||
return all(
|
||||
[
|
||||
(self.is_prod_release or self.branch in ["main", "dockertest"]),
|
||||
self.should_build_docker,
|
||||
self.has_docker_creds,
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def should_upload_aws(self) -> bool:
|
||||
return all(
|
||||
[
|
||||
self.has_aws_creds,
|
||||
(
|
||||
self.should_build_wheel
|
||||
or self.should_build_pyinstaller
|
||||
or self.should_build_wininstaller
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def should_upload_pypi(self) -> bool:
|
||||
return all(
|
||||
[
|
||||
self.is_prod_release,
|
||||
self.should_build_wheel,
|
||||
self.has_twine_creds,
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def upload_dir(self) -> str:
|
||||
if self.tag:
|
||||
return self.version
|
||||
else:
|
||||
return f"branches/{self.version}"
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
if self.tag:
|
||||
if self.tag.startswith("v"):
|
||||
try:
|
||||
parver.Version.parse(self.tag[1:], strict=True)
|
||||
except parver.ParseError:
|
||||
return self.tag
|
||||
return self.tag[1:]
|
||||
return self.tag
|
||||
elif self.branch:
|
||||
return self.branch
|
||||
else:
|
||||
raise BuildError(
|
||||
"We're on neither a tag nor a branch - could not establish version"
|
||||
)
|
||||
|
||||
|
||||
def build_wheel(be: BuildEnviron) -> None: # pragma: no cover
|
||||
click.echo("Building wheel...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"python",
|
||||
"setup.py",
|
||||
"-q",
|
||||
"bdist_wheel",
|
||||
"--dist-dir",
|
||||
be.dist_dir,
|
||||
]
|
||||
)
|
||||
(whl,) = be.dist_dir.glob("mitmproxy-*-py3-none-any.whl")
|
||||
click.echo(f"Found wheel package: {whl}")
|
||||
subprocess.check_call(["tox", "-e", "wheeltest", "--", whl])
|
||||
|
||||
|
||||
DOCKER_PLATFORMS = "linux/amd64,linux/arm64"
|
||||
|
||||
|
||||
def build_docker_image(be: BuildEnviron) -> None: # pragma: no cover
|
||||
click.echo("Building Docker images...")
|
||||
|
||||
(whl,) = be.dist_dir.glob("mitmproxy-*-py3-none-any.whl")
|
||||
docker_build_dir = be.release_dir / "docker"
|
||||
shutil.copy(whl, docker_build_dir / whl.name)
|
||||
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
be.docker_tag,
|
||||
"--platform",
|
||||
DOCKER_PLATFORMS,
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
# smoke-test the newly built docker image
|
||||
|
||||
# build again without --platform but with --load to make the tag available,
|
||||
# see https://github.com/docker/buildx/issues/59#issuecomment-616050491
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
be.docker_tag,
|
||||
"--load",
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
r = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
be.docker_tag,
|
||||
"mitmdump",
|
||||
"--version",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
print(r.stdout.decode())
|
||||
assert "Mitmproxy: " in r.stdout.decode()
|
||||
|
||||
|
||||
def build_pyinstaller(be: BuildEnviron) -> None: # pragma: no cover
|
||||
click.echo("Building pyinstaller package...")
|
||||
|
||||
PYINSTALLER_SPEC = be.release_dir / "specs"
|
||||
PYINSTALLER_TEMP = be.build_dir / "pyinstaller"
|
||||
PYINSTALLER_DIST = be.build_dir / "binaries" / be.platform_tag
|
||||
|
||||
if PYINSTALLER_TEMP.exists():
|
||||
shutil.rmtree(PYINSTALLER_TEMP)
|
||||
if PYINSTALLER_DIST.exists():
|
||||
shutil.rmtree(PYINSTALLER_DIST)
|
||||
|
||||
if be.platform_tag == "windows":
|
||||
with chdir(PYINSTALLER_SPEC):
|
||||
click.echo("Building PyInstaller binaries in directory mode...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"pyinstaller",
|
||||
"--clean",
|
||||
"--workpath",
|
||||
PYINSTALLER_TEMP,
|
||||
"--distpath",
|
||||
PYINSTALLER_DIST,
|
||||
"./windows-dir.spec",
|
||||
]
|
||||
)
|
||||
for tool in ["mitmproxy", "mitmdump", "mitmweb"]:
|
||||
click.echo(f"> {tool} --version")
|
||||
executable = (PYINSTALLER_DIST / "onedir" / tool).with_suffix(".exe")
|
||||
click.echo(subprocess.check_output([executable, "--version"]).decode())
|
||||
|
||||
with be.archive(be.archive_path) as archive:
|
||||
for tool in ["mitmproxy", "mitmdump", "mitmweb"]:
|
||||
# We can't have a folder and a file with the same name.
|
||||
if tool == "mitmproxy":
|
||||
tool = "mitmproxy_main"
|
||||
# Make sure that we are in the spec folder.
|
||||
with chdir(PYINSTALLER_SPEC):
|
||||
click.echo(f"Building PyInstaller {tool} binary...")
|
||||
excludes = []
|
||||
if tool != "mitmweb":
|
||||
excludes.append("mitmproxy.tools.web")
|
||||
if tool != "mitmproxy_main":
|
||||
excludes.append("mitmproxy.tools.console")
|
||||
|
||||
subprocess.check_call(
|
||||
[ # type: ignore
|
||||
"pyinstaller",
|
||||
"--clean",
|
||||
"--workpath",
|
||||
PYINSTALLER_TEMP,
|
||||
"--distpath",
|
||||
PYINSTALLER_DIST,
|
||||
"--onefile",
|
||||
"--console",
|
||||
"--icon",
|
||||
"icon.ico",
|
||||
]
|
||||
+ [x for e in excludes for x in ["--exclude-module", e]]
|
||||
+ [tool]
|
||||
)
|
||||
# Delete the spec file - we're good without.
|
||||
os.remove(f"{tool}.spec")
|
||||
|
||||
executable = PYINSTALLER_DIST / tool
|
||||
if be.platform_tag == "windows":
|
||||
executable = executable.with_suffix(".exe")
|
||||
|
||||
# Remove _main suffix from mitmproxy executable
|
||||
if "_main" in executable.name:
|
||||
executable = executable.rename(
|
||||
executable.with_name(executable.name.replace("_main", ""))
|
||||
)
|
||||
|
||||
# Test if it works at all O:-)
|
||||
click.echo(f"> {executable} --version")
|
||||
click.echo(subprocess.check_output([executable, "--version"]).decode())
|
||||
|
||||
archive.add(str(executable), str(executable.name))
|
||||
click.echo(f"Packed {be.archive_path.name}.")
|
||||
|
||||
|
||||
def build_wininstaller(be: BuildEnviron) -> None: # pragma: no cover
|
||||
click.echo("Building wininstaller package...")
|
||||
|
||||
IB_VERSION = "21.6.0"
|
||||
IB_SETUP_SHA256 = "2bc9f9945cb727ad176aa31fa2fa5a8c57a975bad879c169b93e312af9d05814"
|
||||
IB_DIR = be.release_dir / "installbuilder"
|
||||
IB_SETUP = IB_DIR / "setup" / f"{IB_VERSION}-installer.exe"
|
||||
IB_CLI = Path(
|
||||
fr"C:\Program Files\VMware InstallBuilder Enterprise {IB_VERSION}\bin\builder-cli.exe"
|
||||
)
|
||||
IB_LICENSE = IB_DIR / "license.xml"
|
||||
|
||||
if not IB_LICENSE.exists() and not be.build_key:
|
||||
click.echo("Cannot build windows installer without secret key.")
|
||||
return
|
||||
|
||||
if not IB_CLI.exists():
|
||||
if not IB_SETUP.exists():
|
||||
click.echo("Downloading InstallBuilder...")
|
||||
|
||||
def report(block, blocksize, total):
|
||||
done = block * blocksize
|
||||
if round(100 * done / total) != round(100 * (done - blocksize) / total):
|
||||
click.secho(f"Downloading... {round(100 * done / total)}%")
|
||||
|
||||
tmp = IB_SETUP.with_suffix(".tmp")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://clients.bitrock.com/installbuilder/installbuilder-enterprise-{IB_VERSION}-windows-x64-installer.exe",
|
||||
tmp,
|
||||
reporthook=report,
|
||||
)
|
||||
tmp.rename(IB_SETUP)
|
||||
|
||||
ib_setup_hash = hashlib.sha256()
|
||||
with IB_SETUP.open("rb") as fp:
|
||||
while True:
|
||||
data = fp.read(65_536)
|
||||
if not data:
|
||||
break
|
||||
ib_setup_hash.update(data)
|
||||
if ib_setup_hash.hexdigest() != IB_SETUP_SHA256: # pragma: no cover
|
||||
raise RuntimeError("InstallBuilder hashes don't match.")
|
||||
|
||||
click.echo("Install InstallBuilder...")
|
||||
subprocess.run(
|
||||
[IB_SETUP, "--mode", "unattended", "--unattendedmodeui", "none"], check=True
|
||||
)
|
||||
assert IB_CLI.is_file()
|
||||
|
||||
if not IB_LICENSE.exists():
|
||||
assert be.build_key
|
||||
click.echo("Decrypt InstallBuilder license...")
|
||||
f = cryptography.fernet.Fernet(be.build_key.encode())
|
||||
with open(IB_LICENSE.with_suffix(".xml.enc"), "rb") as infile, open(
|
||||
IB_LICENSE, "wb"
|
||||
) as outfile:
|
||||
outfile.write(f.decrypt(infile.read()))
|
||||
|
||||
click.echo("Run InstallBuilder...")
|
||||
subprocess.run(
|
||||
[
|
||||
IB_CLI,
|
||||
"build",
|
||||
str(IB_DIR / "mitmproxy.xml"),
|
||||
"windows-x64",
|
||||
"--license",
|
||||
str(IB_LICENSE),
|
||||
"--setvars",
|
||||
f"project.version={be.version}",
|
||||
"--verbose",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
assert (be.dist_dir / f"mitmproxy-{be.version}-windows-x64-installer.exe").exists()
|
||||
|
||||
|
||||
@click.group(chain=True)
|
||||
def cli(): # pragma: no cover
|
||||
"""
|
||||
mitmproxy build tool
|
||||
"""
|
||||
|
||||
|
||||
@cli.command("build")
|
||||
def build(): # pragma: no cover
|
||||
"""
|
||||
Build a binary distribution
|
||||
"""
|
||||
be = BuildEnviron.from_env()
|
||||
be.dump_info()
|
||||
|
||||
be.check_version()
|
||||
os.makedirs(be.dist_dir, exist_ok=True)
|
||||
|
||||
if be.should_build_wheel:
|
||||
build_wheel(be)
|
||||
if be.should_build_docker:
|
||||
build_docker_image(be)
|
||||
if be.should_build_pyinstaller:
|
||||
build_pyinstaller(be)
|
||||
if be.should_build_wininstaller:
|
||||
build_wininstaller(be)
|
||||
|
||||
|
||||
@cli.command("upload")
|
||||
def upload(): # pragma: no cover
|
||||
"""
|
||||
Upload build artifacts
|
||||
|
||||
Uploads the wheels package to PyPi.
|
||||
Uploads the Pyinstaller and wheels packages to the snapshot server.
|
||||
Pushes the Docker image to Docker Hub.
|
||||
"""
|
||||
be = BuildEnviron.from_env()
|
||||
be.dump_info()
|
||||
|
||||
if be.is_pull_request:
|
||||
click.echo("Refusing to upload artifacts from a pull request!")
|
||||
return
|
||||
|
||||
if be.should_upload_aws:
|
||||
num_files = len([name for name in be.dist_dir.iterdir() if name.is_file()])
|
||||
click.echo(f"Uploading {num_files} files to AWS dir {be.upload_dir}...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"aws",
|
||||
"s3",
|
||||
"cp",
|
||||
"--acl",
|
||||
"public-read",
|
||||
f"{be.dist_dir}/",
|
||||
f"s3://snapshots.mitmproxy.org/{be.upload_dir}/",
|
||||
"--recursive",
|
||||
]
|
||||
)
|
||||
|
||||
if be.should_upload_pypi:
|
||||
(whl,) = be.dist_dir.glob("mitmproxy-*-py3-none-any.whl")
|
||||
click.echo(f"Uploading {whl} to PyPi...")
|
||||
subprocess.check_call(["twine", "upload", whl])
|
||||
|
||||
if be.should_upload_docker:
|
||||
click.echo(f"Uploading Docker image to tag={be.docker_tag}...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"login",
|
||||
"-u",
|
||||
be.docker_username,
|
||||
"-p",
|
||||
be.docker_password,
|
||||
]
|
||||
)
|
||||
|
||||
(whl,) = be.dist_dir.glob("mitmproxy-*-py3-none-any.whl")
|
||||
docker_build_dir = be.release_dir / "docker"
|
||||
shutil.copy(whl, docker_build_dir / whl.name)
|
||||
# buildx is a bit weird in that we need to reinvoke build, but oh well.
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
be.docker_tag,
|
||||
"--push",
|
||||
"--platform",
|
||||
DOCKER_PLATFORMS,
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
|
||||
if be.is_prod_release:
|
||||
subprocess.check_call(
|
||||
[
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--tag",
|
||||
"mitmproxy/mitmproxy:latest",
|
||||
"--push",
|
||||
"--platform",
|
||||
DOCKER_PLATFORMS,
|
||||
"--build-arg",
|
||||
f"MITMPROXY_WHEEL={whl.name}",
|
||||
".",
|
||||
],
|
||||
cwd=docker_build_dir,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
cli()
|
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
This script submits a single MSIX installer to the Microsoft Store.
|
||||
|
||||
The client_secret will expire after 24 months and needs to be recreated (see docstring below).
|
||||
|
||||
References:
|
||||
- https://docs.microsoft.com/en-us/windows/uwp/monetize/manage-app-submissions
|
||||
- https://docs.microsoft.com/en-us/windows/uwp/monetize/python-code-examples-for-the-windows-store-submission-api
|
||||
- https://docs.microsoft.com/en-us/windows/uwp/monetize/python-code-examples-for-submissions-game-options-and-trailers
|
||||
"""
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
from zipfile import ZipFile
|
||||
|
||||
# Security: No third-party dependencies here!
|
||||
|
||||
assert (
|
||||
os.environ["GITHUB_REF"].startswith("refs/tags/")
|
||||
or os.environ["GITHUB_REF"] == "refs/heads/citest"
|
||||
)
|
||||
|
||||
app_id = os.environ["MSFT_APP_ID"]
|
||||
"""
|
||||
The public application ID / product ID of the app.
|
||||
For https://www.microsoft.com/store/productId/9NWNDLQMNZD7, the app id is 9NWNDLQMNZD7.
|
||||
"""
|
||||
app_flight = os.environ.get("MSFT_APP_FLIGHT", "")
|
||||
"""
|
||||
The application flight we want to target. This is useful to deploy ci test builds to a subset of users.
|
||||
"""
|
||||
tenant_id = os.environ["MSFT_TENANT_ID"]
|
||||
"""
|
||||
The tenant ID for the Azure AD application.
|
||||
https://partner.microsoft.com/en-us/dashboard/account/v3/usermanagement
|
||||
"""
|
||||
client_id = os.environ["MSFT_CLIENT_ID"]
|
||||
"""
|
||||
The client ID for the Azure AD application.
|
||||
https://partner.microsoft.com/en-us/dashboard/account/v3/usermanagement
|
||||
"""
|
||||
client_secret = os.environ["MSFT_CLIENT_SECRET"]
|
||||
"""
|
||||
The client secret. Expires every 24 months and needs to be recreated at
|
||||
https://partner.microsoft.com/en-us/dashboard/account/v3/usermanagement
|
||||
or at https://portal.azure.com/ -> App registrations -> Certificates & Secrets -> Client secrets.
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
_, msi_file = sys.argv
|
||||
except ValueError:
|
||||
print(f"Usage: {sys.argv[0]} installer.msix")
|
||||
sys.exit(1)
|
||||
|
||||
if app_flight:
|
||||
app_id = f"{app_id}/flights/{app_flight}"
|
||||
pending_submission = "pendingFlightSubmission"
|
||||
packages = "flightPackages"
|
||||
else:
|
||||
pending_submission = "pendingApplicationSubmission"
|
||||
packages = "applicationPackages"
|
||||
|
||||
print("Obtaining auth token...")
|
||||
auth = http.client.HTTPSConnection("login.microsoftonline.com")
|
||||
auth.request(
|
||||
"POST",
|
||||
f"/{tenant_id}/oauth2/token",
|
||||
body=urllib.parse.urlencode(
|
||||
{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"resource": "https://manage.devcenter.microsoft.com",
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
)
|
||||
token = json.loads(auth.getresponse().read())["access_token"]
|
||||
auth.close()
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-type": "application/json",
|
||||
"User-Agent": "Python/mitmproxy",
|
||||
}
|
||||
|
||||
|
||||
def request(method: str, path: str, body: str = "") -> bytes:
|
||||
print(f"{method} {path}")
|
||||
conn.request(method, path, body, headers=headers)
|
||||
resp = conn.getresponse()
|
||||
data = resp.read()
|
||||
print(f"{resp.status} {resp.reason}")
|
||||
# noinspection PyUnreachableCode
|
||||
if False:
|
||||
assert "CI" not in os.environ
|
||||
# This contains sensitive data such as the fileUploadUrl, so don't print it in production.
|
||||
print(data.decode(errors="ignore"))
|
||||
assert 200 <= resp.status < 300
|
||||
return data
|
||||
|
||||
|
||||
print("Getting app info...")
|
||||
conn = http.client.HTTPSConnection("manage.devcenter.microsoft.com")
|
||||
# print(request("GET", f"/v1.0/my/applications/{app_id}/listflights"))
|
||||
app_info = json.loads(request("GET", f"/v1.0/my/applications/{app_id}"))
|
||||
|
||||
if pending_submission in app_info:
|
||||
print("Deleting pending submission...")
|
||||
request(
|
||||
"DELETE",
|
||||
f"/v1.0/my/applications/{app_id}/submissions/{app_info[pending_submission]['id']}",
|
||||
)
|
||||
|
||||
print("Creating new submission...")
|
||||
submission = json.loads(request("POST", f"/v1.0/my/applications/{app_id}/submissions"))
|
||||
|
||||
print("Updating submission...")
|
||||
# Mark all existing packages for deletion.
|
||||
for package in submission[packages]:
|
||||
package["fileStatus"] = "PendingDelete"
|
||||
submission[packages].append(
|
||||
{
|
||||
"fileName": f"installer.msix",
|
||||
"fileStatus": "PendingUpload",
|
||||
"minimumDirectXVersion": "None",
|
||||
"minimumSystemRam": "None",
|
||||
}
|
||||
)
|
||||
request(
|
||||
"PUT",
|
||||
f"/v1.0/my/applications/{app_id}/submissions/{submission['id']}",
|
||||
json.dumps(submission),
|
||||
)
|
||||
conn.close()
|
||||
|
||||
print(f"Zipping {msi_file}...")
|
||||
with tempfile.TemporaryFile() as zipfile:
|
||||
with ZipFile(zipfile, "w") as f:
|
||||
f.write(msi_file, f"installer.msix")
|
||||
zip_size = zipfile.tell()
|
||||
zipfile.seek(0)
|
||||
|
||||
print("Uploading zip file...")
|
||||
host, _, path = submission["fileUploadUrl"].removeprefix("https://").partition("/")
|
||||
upload = http.client.HTTPSConnection(host)
|
||||
upload.request(
|
||||
"PUT",
|
||||
"/" + path,
|
||||
zipfile,
|
||||
{
|
||||
"x-ms-blob-type": "BlockBlob",
|
||||
"x-ms-version": "2019-12-12",
|
||||
"Content-Length": str(zip_size),
|
||||
},
|
||||
)
|
||||
resp = upload.getresponse()
|
||||
resp.read()
|
||||
print(resp.status, resp.reason)
|
||||
assert 200 <= resp.status < 300
|
||||
upload.close()
|
||||
|
||||
print("Publishing submission...")
|
||||
# previous connection has timed out during upload.
|
||||
conn = http.client.HTTPSConnection("manage.devcenter.microsoft.com")
|
||||
request("POST", f"/v1.0/my/applications/{app_id}/submissions/{submission['id']}/commit")
|
||||
# We could wait until it's published here, but CI is billed by the minute.
|
||||
# resp = request("GET", f"/v1.0/my/applications/{app_id}/submissions/{submission['id']}/status")
|
||||
conn.close()
|
|
@ -1,12 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Security: No third-party dependencies here!
|
||||
|
||||
root = Path(__file__).absolute().parent.parent
|
||||
|
||||
if __name__ == "__main__":
|
||||
ref = os.environ["GITHUB_REF"]
|
||||
branch: Optional[str] = None
|
||||
|
@ -20,10 +21,10 @@ if __name__ == "__main__":
|
|||
|
||||
# Upload binaries (be it release or snapshot)
|
||||
if tag:
|
||||
# remove "v" prefix from version tags.
|
||||
upload_dir = re.sub(r"^v([\d.]+)$", r"\1", tag)
|
||||
upload_dir = tag
|
||||
else:
|
||||
upload_dir = f"branches/{branch}"
|
||||
print(f"Uploading binaries to snapshots.mitmproxy.org/{upload_dir}...")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"aws",
|
||||
|
@ -31,7 +32,7 @@ if __name__ == "__main__":
|
|||
"cp",
|
||||
"--acl",
|
||||
"public-read",
|
||||
f"./release/dist/",
|
||||
root / "release/dist",
|
||||
f"s3://snapshots.mitmproxy.org/{upload_dir}/",
|
||||
"--recursive",
|
||||
]
|
||||
|
@ -39,11 +40,13 @@ if __name__ == "__main__":
|
|||
|
||||
# Upload releases to PyPI
|
||||
if tag:
|
||||
(whl,) = Path("release/dist/").glob("mitmproxy-*-py3-none-any.whl")
|
||||
print(f"Uploading wheel to PyPI...")
|
||||
(whl,) = root.glob("release/dist/mitmproxy-*-py3-none-any.whl")
|
||||
subprocess.check_call(["twine", "upload", whl])
|
||||
|
||||
# Upload dev docs
|
||||
if branch == "main" or branch == "actions-hardening": # FIXME remove
|
||||
if branch == "main":
|
||||
print(f"Uploading dev docs...")
|
||||
subprocess.check_call(["aws", "configure", "set", "preview.cloudfront", "true"])
|
||||
subprocess.check_call(
|
||||
[
|
||||
|
@ -53,7 +56,7 @@ if __name__ == "__main__":
|
|||
"--delete",
|
||||
"--acl",
|
||||
"public-read",
|
||||
"docs/public",
|
||||
root / "docs/public",
|
||||
"s3://docs.mitmproxy.org/dev",
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
from mitmproxy.tools.main import mitmproxy
|
||||
mitmproxy()
|
|
@ -0,0 +1,26 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
for tool in ["mitmproxy", "mitmdump", "mitmweb"]:
|
||||
excludes = []
|
||||
if tool != "mitmweb":
|
||||
excludes.append("mitmproxy.tools.web")
|
||||
if tool != "mitmproxy":
|
||||
excludes.append("mitmproxy.tools.console")
|
||||
|
||||
a = Analysis(
|
||||
[tool],
|
||||
excludes=excludes,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data)
|
||||
|
||||
EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name=tool,
|
||||
console=True,
|
||||
icon='icon.ico',
|
||||
)
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
|
||||
>
|
||||
<!--
|
||||
<Identity Name="8637MaximilianHils.mitmproxy" ProcessorArchitecture="x64" Publisher="CN=E4E2D0A5-1FD2-42BF-B9FD-3BCA3E1C1249, OID.2.25.311729368913984317654407730594956997722=1" Version="1.2.3.0" />
|
||||
Then from an admin shell:
|
||||
Add-AppPackage -Path .\release\dist\mitmproxy-dev-installer.msix -AllowUnsigned
|
||||
-->
|
||||
<Identity Name="8637MaximilianHils.mitmproxy" ProcessorArchitecture="x64" Publisher="CN=E4E2D0A5-1FD2-42BF-B9FD-3BCA3E1C1249" Version="1.2.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>mitmproxy</DisplayName>
|
||||
<PublisherDisplayName>mitmproxy.org</PublisherDisplayName>
|
||||
<Description>mitmproxy is a free and open source interactive HTTPS proxy.</Description>
|
||||
<Logo>Assets\logo.150x150.png</Logo>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22622.436" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="mitmproxy" Executable="mitmproxy.exe" EntryPoint="Windows.FullTrustApplication" uap10:Subsystem="console" uap10:SupportsMultipleInstances="true">
|
||||
<uap3:VisualElements DisplayName="mitmproxy (console ui)" Description="Mitmproxy Console UI" BackgroundColor="#333333"
|
||||
Square150x150Logo="Assets\logo.150x150.png" Square44x44Logo="Assets\logo.44x44.png" VisualGroup="mitmproxy"/>
|
||||
<Extensions>
|
||||
<uap3:Extension Executable="mitmproxy.exe" Category="windows.appExecutionAlias" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="mitmproxy.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
<Application Id="mitmweb" Executable="mitmweb.exe" EntryPoint="Windows.FullTrustApplication" uap10:Subsystem="console" uap10:SupportsMultipleInstances="true">
|
||||
<uap3:VisualElements DisplayName="mitmweb" Description="Mitmproxy Web UI" BackgroundColor="#333333"
|
||||
Square150x150Logo="Assets\logo.150x150.png" Square44x44Logo="Assets\logo.44x44.png" VisualGroup="mitmproxy"/>
|
||||
<Extensions>
|
||||
<uap3:Extension Executable="mitmweb.exe" Category="windows.appExecutionAlias" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="mitmweb.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
<Application Id="mitmdump" Executable="mitmdump.exe" EntryPoint="Windows.FullTrustApplication" uap10:Subsystem="console" uap10:SupportsMultipleInstances="true">
|
||||
<uap3:VisualElements DisplayName="mitmdump" Description="Mitmdump" BackgroundColor="#333333"
|
||||
Square150x150Logo="Assets\logo.150x150.png" Square44x44Logo="Assets\logo.44x44.png" VisualGroup="mitmproxy"/>
|
||||
<Extensions>
|
||||
<uap3:Extension Executable="mitmdump.exe" Category="windows.appExecutionAlias" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="mitmdump.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Extensions>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="mitmproxy.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="mitmweb.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="mitmdump.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" LocalPortMin="1" LocalPortMax="65535" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
</Package>
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
@ -1,35 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
|
||||
<Identity Name="Mitmproxy" ProcessorArchitecture="x86" Publisher="CN=E4E2D0A5-1FD2-42BF-B9FD-3BCA3E1C" Version="2.0.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Mitmproxy</DisplayName>
|
||||
<PublisherDisplayName>Maximilian Hils</PublisherDisplayName>
|
||||
<Logo>Assets\logo.44x44.png</Logo>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.15063.296" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="mitmweb" Executable="mitmweb.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:VisualElements DisplayName="Mitmproxy UI" Description="Mitmproxy Web UI" BackgroundColor="#333333"
|
||||
Square150x150Logo="Assets\logo.150x150.png" Square44x44Logo="Assets\logo.44x44.png"/>
|
||||
<Extensions>
|
||||
<uap3:Extension Executable="mitmweb.exe" Category="windows.appExecutionAlias" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="mitmweb.exe" />
|
||||
<desktop:ExecutionAlias Alias="mitmproxy.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
|
@ -1,20 +0,0 @@
|
|||
# Mitmproxy on the Windows Store
|
||||
|
||||
@mhils experimented with bringing mitmproxy to the Window Store using the Desktop Bridge. This would replace our current InstallBuilder setup and allow for clean installs and - more importantly - automatic updates.
|
||||
|
||||
## Advantages
|
||||
|
||||
- Automatic updates
|
||||
- Clean installs
|
||||
- Very simple setup on our end
|
||||
- Possibility to roll out experimental releases to a subset of users
|
||||
|
||||
## Disadvantages
|
||||
|
||||
- No support for mitmproxy. That only runs under WSL. Making WSL nicer is a complementary effort.
|
||||
- "Your developer account doesn’t have permission to submit apps converted with the Desktop Bridge at this time." (requested)
|
||||
- New releases need to be submitted manually (Submission API is in preview).
|
||||
|
||||
## Notes
|
||||
|
||||
We do not want to force anyone to use this, we would of course keep our portable binaries (and, of course, WSL).
|
|
@ -1,240 +0,0 @@
|
|||
import io
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from release import cibuild
|
||||
|
||||
root = Path(__file__).parent.parent.parent
|
||||
|
||||
|
||||
def test_buildenviron_live():
|
||||
be = cibuild.BuildEnviron.from_env()
|
||||
assert be.release_dir
|
||||
|
||||
|
||||
def test_buildenviron_common():
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
branch="main",
|
||||
)
|
||||
assert be.release_dir == be.root_dir / "release"
|
||||
assert be.dist_dir == be.root_dir / "release" / "dist"
|
||||
assert be.build_dir == be.root_dir / "release" / "build"
|
||||
assert not be.has_docker_creds
|
||||
|
||||
cs = io.StringIO()
|
||||
be.dump_info(cs)
|
||||
assert cs.getvalue()
|
||||
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Unknown",
|
||||
root_dir=root,
|
||||
)
|
||||
with pytest.raises(cibuild.BuildError):
|
||||
be.version
|
||||
with pytest.raises(cibuild.BuildError):
|
||||
be.platform_tag
|
||||
|
||||
|
||||
def test_buildenviron_pr(monkeypatch):
|
||||
# Simulates a PR. We build everything, but don't have access to secret
|
||||
# credential env variables.
|
||||
monkeypatch.setenv("GITHUB_REF", "refs/pull/42/merge")
|
||||
monkeypatch.setenv("CI_BUILD_WHEEL", "1")
|
||||
monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request")
|
||||
|
||||
be = cibuild.BuildEnviron.from_env()
|
||||
assert be.branch == "pr-42"
|
||||
assert be.is_pull_request
|
||||
assert be.should_build_wheel
|
||||
assert not be.should_upload_pypi
|
||||
|
||||
|
||||
def test_buildenviron_commit():
|
||||
# Simulates an ordinary commit on the master branch.
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
branch="main",
|
||||
is_pull_request=False,
|
||||
should_build_wheel=True,
|
||||
should_build_pyinstaller=True,
|
||||
should_build_docker=True,
|
||||
docker_username="foo",
|
||||
docker_password="bar",
|
||||
has_aws_creds=True,
|
||||
)
|
||||
assert be.docker_tag == "mitmproxy/mitmproxy:dev"
|
||||
assert be.should_upload_docker
|
||||
assert not be.should_upload_pypi
|
||||
assert be.should_upload_docker
|
||||
assert be.should_upload_aws
|
||||
assert not be.is_prod_release
|
||||
assert not be.is_maintenance_branch
|
||||
|
||||
|
||||
def test_buildenviron_releasetag():
|
||||
# Simulates a tagged release on a release branch.
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
tag="v0.0.1",
|
||||
should_build_wheel=True,
|
||||
should_build_docker=True,
|
||||
should_build_pyinstaller=True,
|
||||
has_twine_creds=True,
|
||||
docker_username="foo",
|
||||
docker_password="bar",
|
||||
)
|
||||
assert be.tag == "v0.0.1"
|
||||
assert be.branch is None
|
||||
assert be.version == "0.0.1"
|
||||
assert be.upload_dir == "0.0.1"
|
||||
assert be.docker_tag == "mitmproxy/mitmproxy:0.0.1"
|
||||
assert be.should_upload_pypi
|
||||
assert be.should_upload_docker
|
||||
assert be.is_prod_release
|
||||
assert not be.is_maintenance_branch
|
||||
|
||||
|
||||
def test_buildenviron_namedtag():
|
||||
# Simulates a non-release tag on a branch.
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
tag="anyname",
|
||||
should_build_wheel=True,
|
||||
should_build_docker=True,
|
||||
should_build_pyinstaller=True,
|
||||
has_twine_creds=True,
|
||||
docker_username="foo",
|
||||
docker_password="bar",
|
||||
)
|
||||
assert be.tag == "anyname"
|
||||
assert be.branch is None
|
||||
assert be.version == "anyname"
|
||||
assert be.upload_dir == "anyname"
|
||||
assert be.docker_tag == "mitmproxy/mitmproxy:anyname"
|
||||
assert not be.should_upload_pypi
|
||||
assert not be.should_upload_docker
|
||||
assert not be.is_prod_release
|
||||
assert not be.is_maintenance_branch
|
||||
|
||||
|
||||
def test_buildenviron_dev_branch():
|
||||
# Simulates a commit on a development branch on the main repo
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
branch="mybranch",
|
||||
should_build_wheel=True,
|
||||
should_build_docker=True,
|
||||
should_build_pyinstaller=True,
|
||||
has_twine_creds=True,
|
||||
docker_username="foo",
|
||||
docker_password="bar",
|
||||
)
|
||||
assert be.tag is None
|
||||
assert be.branch == "mybranch"
|
||||
assert be.version == "mybranch"
|
||||
assert be.upload_dir == "branches/mybranch"
|
||||
assert not be.should_upload_pypi
|
||||
assert not be.should_upload_docker
|
||||
assert not be.is_maintenance_branch
|
||||
|
||||
|
||||
def test_buildenviron_maintenance_branch():
|
||||
# Simulates a commit on a release maintenance branch on the main repo
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Linux",
|
||||
root_dir=root,
|
||||
branch="v0.x",
|
||||
should_build_wheel=True,
|
||||
should_build_docker=True,
|
||||
should_build_pyinstaller=True,
|
||||
has_twine_creds=True,
|
||||
docker_username="foo",
|
||||
docker_password="bar",
|
||||
)
|
||||
assert be.tag is None
|
||||
assert be.branch == "v0.x"
|
||||
assert be.version == "v0.x"
|
||||
assert be.upload_dir == "branches/v0.x"
|
||||
assert not be.should_upload_pypi
|
||||
assert not be.should_upload_docker
|
||||
assert be.is_maintenance_branch
|
||||
|
||||
|
||||
def test_buildenviron_osx(tmp_path):
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Darwin",
|
||||
root_dir=root,
|
||||
tag="v0.0.1",
|
||||
)
|
||||
assert be.platform_tag == "osx"
|
||||
assert be.archive_path == be.dist_dir / "mitmproxy-0.0.1-osx.tar.gz"
|
||||
|
||||
with be.archive(tmp_path / "arch"):
|
||||
pass
|
||||
assert (tmp_path / "arch").exists()
|
||||
|
||||
|
||||
def test_buildenviron_windows(tmp_path):
|
||||
be = cibuild.BuildEnviron(
|
||||
system="Windows",
|
||||
root_dir=root,
|
||||
tag="v0.0.1",
|
||||
)
|
||||
assert be.platform_tag == "windows"
|
||||
assert be.archive_path == be.dist_dir / "mitmproxy-0.0.1-windows.zip"
|
||||
|
||||
with be.archive(tmp_path / "arch"):
|
||||
pass
|
||||
assert (tmp_path / "arch").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"version, tag, ok",
|
||||
[
|
||||
("3.0.0.dev", "", True), # regular snapshot
|
||||
("3.0.0.dev", "v3.0.0", False), # forgot to remove ".dev" on bump
|
||||
("3.0.0", "", False), # forgot to re-add ".dev"
|
||||
("3.0.0", "v4.0.0", False), # version mismatch
|
||||
("3.0.0", "v3.0.0", True), # regular release
|
||||
("3.0.0.rc1", "v3.0.0.rc1", False), # non-canonical.
|
||||
("3.0.0.dev", "anyname", True), # tagged test/dev release
|
||||
("3.0.0", "3.0.0", False), # tagged, but without v prefix
|
||||
],
|
||||
)
|
||||
def test_buildenviron_check_version(version, tag, ok, tmpdir):
|
||||
tmpdir.mkdir("mitmproxy").join("version.py").write(f'VERSION = "{version}"')
|
||||
|
||||
be = cibuild.BuildEnviron(
|
||||
root_dir=tmpdir,
|
||||
system="Windows",
|
||||
tag=tag,
|
||||
)
|
||||
if ok:
|
||||
be.check_version()
|
||||
else:
|
||||
with pytest.raises(ValueError):
|
||||
be.check_version()
|
||||
|
||||
|
||||
def test_bool_from_env(monkeypatch):
|
||||
monkeypatch.setenv("FOO", "1")
|
||||
assert cibuild.bool_from_env("FOO")
|
||||
|
||||
monkeypatch.setenv("FOO", "0")
|
||||
assert not cibuild.bool_from_env("FOO")
|
||||
|
||||
monkeypatch.setenv("FOO", "false")
|
||||
assert not cibuild.bool_from_env("FOO")
|
||||
|
||||
monkeypatch.setenv("FOO", "")
|
||||
assert not cibuild.bool_from_env("FOO")
|
||||
|
||||
monkeypatch.delenv("FOO")
|
||||
assert not cibuild.bool_from_env("FOO")
|
Loading…
Reference in New Issue