Add Vagrant, fixes for CI/CD pipeline, and Windows dev bootstrap script
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:community.chocolatey.org)",
|
||||
"WebFetch(domain:github.com)"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+310
-1
@@ -308,6 +308,46 @@ d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""]
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.42.73"
|
||||
description = "The AWS SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "boto3-1.42.73-py3-none-any.whl", hash = "sha256:1f81b79b873f130eeab14bb556417a7c66d38f3396b7f2fe3b958b3f9094f455"},
|
||||
{file = "boto3-1.42.73.tar.gz", hash = "sha256:d37b58d6cd452ca808dd6823ae19ca65b6244096c5125ef9052988b337298bae"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.42.73,<1.43.0"
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
s3transfer = ">=0.16.0,<0.17.0"
|
||||
|
||||
[package.extras]
|
||||
crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.42.73"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "botocore-1.42.73-py3-none-any.whl", hash = "sha256:7b62e2a12f7a1b08eb7360eecd23bb16fe3b7ab7f5617cf91b25476c6f86a0fe"},
|
||||
{file = "botocore-1.42.73.tar.gz", hash = "sha256:575858641e4949aaf2af1ced145b8524529edf006d075877af6b82ff96ad854c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
python-dateutil = ">=2.1,<3.0.0"
|
||||
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
crt = ["awscrt (==0.31.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "7.0.5"
|
||||
@@ -1135,6 +1175,88 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto
|
||||
test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd ; python_version < \"3.14\"", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas (<3.0.0)", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard ; python_version < \"3.14\""]
|
||||
tqdm = ["tqdm"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.30.0"
|
||||
description = "Google API client core library"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "google_api_core-2.30.0-py3-none-any.whl", hash = "sha256:80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5"},
|
||||
{file = "google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-auth = ">=2.14.1,<3.0.0"
|
||||
googleapis-common-protos = ">=1.56.3,<2.0.0"
|
||||
grpcio = [
|
||||
{version = ">=1.75.1,<2.0.0", optional = true, markers = "python_version >= \"3.14\" and extra == \"grpc\""},
|
||||
{version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
grpcio-status = [
|
||||
{version = ">=1.75.1,<2.0.0", optional = true, markers = "python_version >= \"3.14\" and extra == \"grpc\""},
|
||||
{version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
proto-plus = {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}
|
||||
protobuf = ">=4.25.8,<7.0.0"
|
||||
requests = ">=2.20.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"]
|
||||
grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""]
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.49.1"
|
||||
description = "Google Authentication Library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7"},
|
||||
{file = "google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=38.0.3"
|
||||
pyasn1-modules = ">=0.2.1"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"]
|
||||
cryptography = ["cryptography (>=38.0.3)"]
|
||||
enterprise-cert = ["pyopenssl"]
|
||||
pyjwt = ["pyjwt (>=2.0)"]
|
||||
pyopenssl = ["pyopenssl (>=20.0.0)"]
|
||||
reauth = ["pyu2f (>=0.1.5)"]
|
||||
requests = ["requests (>=2.20.0,<3.0.0)"]
|
||||
rsa = ["rsa (>=3.1.4,<5)"]
|
||||
testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "flask", "freezegun", "grpcio", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"]
|
||||
urllib3 = ["packaging", "urllib3"]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-secret-manager"
|
||||
version = "2.26.0"
|
||||
description = "Google Cloud Secret Manager API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "google_cloud_secret_manager-2.26.0-py3-none-any.whl", hash = "sha256:940a5447a6ec9951446fd1a0f22c81a4303fde164cd747aae152c5f5c8e6723e"},
|
||||
{file = "google_cloud_secret_manager-2.26.0.tar.gz", hash = "sha256:0d1d6f76327685a0ed78a4cf50f289e1bfbbe56026ed0affa98663b86d6d50d6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]}
|
||||
google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0"
|
||||
grpc-google-iam-v1 = ">=0.14.0,<1.0.0"
|
||||
grpcio = [
|
||||
{version = ">=1.75.1,<2.0.0", markers = "python_version >= \"3.14\""},
|
||||
{version = ">=1.33.2,<2.0.0", markers = "python_version < \"3.14\""},
|
||||
]
|
||||
proto-plus = {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}
|
||||
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.73.0"
|
||||
@@ -1148,6 +1270,7 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""}
|
||||
protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -1328,6 +1451,117 @@ files = [
|
||||
[package.extras]
|
||||
pypi = ["pip (>=24.0)", "platformdirs (>=4.2)", "wheel (>=0.42)"]
|
||||
|
||||
[[package]]
|
||||
name = "grpc-google-iam-v1"
|
||||
version = "0.14.3"
|
||||
description = "IAM API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6"},
|
||||
{file = "grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
googleapis-common-protos = {version = ">=1.56.0,<2.0.0", extras = ["grpc"]}
|
||||
grpcio = ">=1.44.0,<2.0.0"
|
||||
protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.78.0"
|
||||
description = "HTTP/2-based RPC framework"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de"},
|
||||
{file = "grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856"},
|
||||
{file = "grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68"},
|
||||
{file = "grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670"},
|
||||
{file = "grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c"},
|
||||
{file = "grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-win32.whl", hash = "sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525"},
|
||||
{file = "grpcio-1.78.0-cp39-cp39-win_amd64.whl", hash = "sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df"},
|
||||
{file = "grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.12,<5.0"
|
||||
|
||||
[package.extras]
|
||||
protobuf = ["grpcio-tools (>=1.78.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio-status"
|
||||
version = "1.78.0"
|
||||
description = "Status proto mapping for gRPC"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34"},
|
||||
{file = "grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
googleapis-common-protos = ">=1.5.5"
|
||||
grpcio = ">=1.78.0"
|
||||
protobuf = ">=6.31.1,<7.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
@@ -1543,6 +1777,18 @@ files = [
|
||||
humanize = ">=3.14.0"
|
||||
jinja2 = "*"
|
||||
|
||||
[[package]]
|
||||
name = "jmespath"
|
||||
version = "1.1.0"
|
||||
description = "JSON Matching Expressions"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64"},
|
||||
{file = "jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonpatch"
|
||||
version = "1.33"
|
||||
@@ -2814,6 +3060,24 @@ files = [
|
||||
{file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proto-plus"
|
||||
version = "1.27.1"
|
||||
description = "Beautiful, Pythonic protocol buffers"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "proto_plus-1.27.1-py3-none-any.whl", hash = "sha256:e4643061f3a4d0de092d62aa4ad09fa4756b2cbb89d4627f3985018216f9fefc"},
|
||||
{file = "proto_plus-1.27.1.tar.gz", hash = "sha256:912a7460446625b792f6448bade9e55cd4e41e6ac10e27009ef71a7f317fa147"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
protobuf = ">=3.19.0,<7.0.0"
|
||||
|
||||
[package.extras]
|
||||
testing = ["google-api-core (>=1.31.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.33.6"
|
||||
@@ -2876,6 +3140,33 @@ valkey = ["valkey-glide (>=2.1.0)"]
|
||||
vault = ["hvac (>=2.3.0)", "types-hvac (>=2.3.0)"]
|
||||
wrappers-encryption = ["cryptography (>=45.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.3"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde"},
|
||||
{file = "pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1-modules"
|
||||
version = "0.4.2"
|
||||
description = "A collection of ASN.1-based protocols modules"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
|
||||
{file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.6.1,<0.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.14.0"
|
||||
@@ -4033,6 +4324,24 @@ files = [
|
||||
{file = "ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "s3transfer"
|
||||
version = "0.16.0"
|
||||
description = "An Amazon S3 Transfer Manager"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["ci"]
|
||||
files = [
|
||||
{file = "s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe"},
|
||||
{file = "s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.37.4,<2.0a0"
|
||||
|
||||
[package.extras]
|
||||
crt = ["botocore[crt] (>=1.37.4,<2.0a0)"]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "3.0.4"
|
||||
@@ -4857,4 +5166,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.13, <3.15"
|
||||
content-hash = "7b655afe35515359a21bee45caa6d7f5fb221506565198101b70392eaecb63e4"
|
||||
content-hash = "1dc0bb464d30417e0477b11e10ba84fca942bb3b59bdb3affa1e9068eefd1153"
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
# bootstrap.ps1 — Windows developer environment bootstrapper for Android File Handler
|
||||
# - Installs Chocolatey (if missing)
|
||||
# - Installs Vagrant (Chocolatey or direct from HashiCorp)
|
||||
# - Installs pyenv-win (if missing)
|
||||
# - Installs Python build dependencies (Chocolatey)
|
||||
# - Installs zlib from source (https://zlib.net/current/zlib.tar.gz)
|
||||
# - Installs libffi from GitHub releases (https://github.com/libffi/libffi)
|
||||
# - Installs latest Python (from pyproject.toml)
|
||||
# - Sets that Python as global default
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "=== Android File Handler Windows Dev Bootstrap ===" -ForegroundColor Cyan
|
||||
|
||||
# --- Install Chocolatey ---
|
||||
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing Chocolatey..." -ForegroundColor Yellow
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
}
|
||||
|
||||
# --- Install Vagrant ---
|
||||
$vagrantInstalled = Get-Command vagrant -ErrorAction SilentlyContinue
|
||||
if (-not $vagrantInstalled) {
|
||||
Write-Host "Installing Vagrant..." -ForegroundColor Yellow
|
||||
$chocoAvailable = Get-Command choco -ErrorAction SilentlyContinue
|
||||
if ($chocoAvailable) {
|
||||
Write-Host " Installing via Chocolatey..." -ForegroundColor Yellow
|
||||
choco install vagrant -y --no-progress
|
||||
}
|
||||
else {
|
||||
Write-Host " Chocolatey not available — downloading from HashiCorp..." -ForegroundColor Yellow
|
||||
$vagrantVersion = "2.4.9"
|
||||
$msiUrl = "https://releases.hashicorp.com/vagrant/$vagrantVersion/vagrant_${vagrantVersion}_windows_amd64.msi"
|
||||
$msiPath = "$env:TEMP\vagrant_${vagrantVersion}_windows_amd64.msi"
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
Write-Host " Downloading $msiUrl ..." -ForegroundColor Yellow
|
||||
(New-Object System.Net.WebClient).DownloadFile($msiUrl, $msiPath)
|
||||
Write-Host " Installing MSI..." -ForegroundColor Yellow
|
||||
$msiProc = Start-Process -FilePath "msiexec.exe" `
|
||||
-ArgumentList "/i", $msiPath, "/qn", "/norestart" `
|
||||
-Wait -PassThru
|
||||
if ($msiProc.ExitCode -ne 0) {
|
||||
Write-Host "ERROR: Vagrant MSI install failed with exit code $($msiProc.ExitCode)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Remove-Item $msiPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
Write-Host " Vagrant installed: $(vagrant --version)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "Vagrant already installed: $(vagrant --version)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- Install pyenv-win ---
|
||||
$pyenvRoot = "$env:USERPROFILE\.pyenv"
|
||||
$pyenvBin = "$pyenvRoot\pyenv-win\bin\pyenv.bat"
|
||||
if (-not (Test-Path $pyenvBin)) {
|
||||
Write-Host "Installing pyenv-win..." -ForegroundColor Yellow
|
||||
choco install pyenv-win -y --no-progress
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
}
|
||||
else {
|
||||
Write-Host "pyenv-win already installed." -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- Install Python build dependencies ---
|
||||
# Package IDs verified against https://community.chocolatey.org/packages
|
||||
# zlib has no Chocolatey package; libffi has no Chocolatey package — both omitted
|
||||
$buildDeps = @('visualcpp-build-tools', 'make', 'openssl.light', 'bzip2', 'SQLite')
|
||||
$installedPkgs = choco list --no-progress --limit-output 2>&1 |
|
||||
Where-Object { $_ -match '\|' } |
|
||||
ForEach-Object { ($_ -split '\|')[0].ToLower() }
|
||||
$toInstall = @($buildDeps | Where-Object { $_.ToLower() -notin $installedPkgs })
|
||||
if ($toInstall.Count -gt 0) {
|
||||
Write-Host "Installing Python build dependencies: $($toInstall -join ', ')..." -ForegroundColor Yellow
|
||||
choco install -y --no-progress @toInstall
|
||||
} else {
|
||||
Write-Host "Python build dependencies already installed." -ForegroundColor Green
|
||||
}
|
||||
|
||||
|
||||
# Shared install root for libraries not on Chocolatey
|
||||
$devLibsRoot = "$env:USERPROFILE\.devlibs"
|
||||
New-Item -ItemType Directory -Path $devLibsRoot -Force | Out-Null
|
||||
|
||||
# --- Install zlib from source (https://zlib.net/current/zlib.tar.gz) ---
|
||||
$zlibInstallDir = "$devLibsRoot\zlib"
|
||||
if (-not (Test-Path "$zlibInstallDir\include\zlib.h")) {
|
||||
Write-Host "Installing zlib from source..." -ForegroundColor Yellow
|
||||
|
||||
# Locate MSVC build environment via vswhere
|
||||
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $vswhere)) {
|
||||
Write-Host "ERROR: vswhere.exe not found — Visual Studio Build Tools required to build zlib." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$vsPath = (& $vswhere -latest -products * -requires Microsoft.VisualCpp.Tools.HostX86.TargetX64 -property installationPath 2>&1).Trim()
|
||||
$vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat"
|
||||
if (-not (Test-Path $vcvarsall)) {
|
||||
Write-Host "ERROR: vcvarsall.bat not found at $vcvarsall" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Download source tarball
|
||||
$zlibTar = "$env:TEMP\zlib.tar.gz"
|
||||
Write-Host " Downloading zlib source..." -ForegroundColor Yellow
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
(New-Object System.Net.WebClient).DownloadFile('https://zlib.net/current/zlib.tar.gz', $zlibTar)
|
||||
|
||||
# Extract (tar.exe ships with Windows 10 1803+)
|
||||
$zlibBuildDir = "$env:TEMP\zlib-build"
|
||||
if (Test-Path $zlibBuildDir) { Remove-Item $zlibBuildDir -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $zlibBuildDir | Out-Null
|
||||
tar -xzf $zlibTar -C $zlibBuildDir --strip-components=1
|
||||
|
||||
# Build with MSVC nmake via a wrapper cmd script
|
||||
Write-Host " Building zlib with MSVC nmake..." -ForegroundColor Yellow
|
||||
$buildCmd = "$env:TEMP\zlib-build.cmd"
|
||||
"@echo off`r`ncall `"$vcvarsall`" x64`r`ncd /d `"$zlibBuildDir`"`r`nnmake -f win32\Makefile.msc`r`n" |
|
||||
Set-Content $buildCmd -Encoding ASCII
|
||||
$proc = Start-Process cmd.exe -ArgumentList "/c `"$buildCmd`"" -Wait -PassThru -NoNewWindow
|
||||
Remove-Item $buildCmd -Force -ErrorAction SilentlyContinue
|
||||
if ($proc.ExitCode -ne 0) {
|
||||
Write-Host "ERROR: zlib build failed (exit $($proc.ExitCode))" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Install headers and libs
|
||||
New-Item -ItemType Directory -Path "$zlibInstallDir\include" -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path "$zlibInstallDir\lib" -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path "$zlibInstallDir\bin" -Force | Out-Null
|
||||
Copy-Item "$zlibBuildDir\zlib.h" "$zlibInstallDir\include\"
|
||||
Copy-Item "$zlibBuildDir\zconf.h" "$zlibInstallDir\include\"
|
||||
Copy-Item "$zlibBuildDir\zlib.lib" "$zlibInstallDir\lib\"
|
||||
Copy-Item "$zlibBuildDir\zdll.lib" "$zlibInstallDir\lib\" -ErrorAction SilentlyContinue
|
||||
Copy-Item "$zlibBuildDir\zlib1.dll" "$zlibInstallDir\bin\" -ErrorAction SilentlyContinue
|
||||
|
||||
# Cleanup
|
||||
Remove-Item $zlibTar -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $zlibBuildDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "zlib installed to $zlibInstallDir" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "zlib already installed at $zlibInstallDir" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Add zlib bin dir to user PATH (persists across sessions; also updates current session)
|
||||
$zlibBinDir = "$zlibInstallDir\bin"
|
||||
$userPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if ($userPath -notmatch [regex]::Escape($zlibBinDir)) {
|
||||
[System.Environment]::SetEnvironmentVariable('Path', "$userPath;$zlibBinDir", 'User')
|
||||
Write-Host " Added $zlibBinDir to user PATH" -ForegroundColor Green
|
||||
}
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' +
|
||||
[System.Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
|
||||
# --- Install libffi from GitHub releases (https://github.com/libffi/libffi) ---
|
||||
$libffiInstallDir = "$devLibsRoot\libffi"
|
||||
if (-not (Test-Path "$libffiInstallDir\include\ffi.h")) {
|
||||
Write-Host "Fetching latest libffi release from GitHub..." -ForegroundColor Yellow
|
||||
|
||||
$apiResponse = Invoke-RestMethod `
|
||||
-Uri 'https://api.github.com/repos/libffi/libffi/releases/latest' `
|
||||
-Headers @{ 'User-Agent' = 'android-file-handler-bootstrap' }
|
||||
$libffiTag = $apiResponse.tag_name # e.g. "v3.5.2"
|
||||
$libffiVersion = $libffiTag -replace '^v', '' # e.g. "3.5.2"
|
||||
Write-Host " Latest libffi: $libffiTag" -ForegroundColor Yellow
|
||||
|
||||
$zipName = "libffi-$libffiVersion-x86-64bit-msvc-binaries.zip"
|
||||
$downloadUrl = "https://github.com/libffi/libffi/releases/download/$libffiTag/$zipName"
|
||||
$zipPath = "$env:TEMP\$zipName"
|
||||
|
||||
Write-Host " Downloading $zipName..." -ForegroundColor Yellow
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
(New-Object System.Net.WebClient).DownloadFile($downloadUrl, $zipPath)
|
||||
|
||||
# Extract — zip contains a single top-level subdirectory
|
||||
$extractTemp = "$env:TEMP\libffi-extract"
|
||||
if (Test-Path $extractTemp) { Remove-Item $extractTemp -Recurse -Force }
|
||||
Expand-Archive -Path $zipPath -DestinationPath $extractTemp -Force
|
||||
|
||||
if (Test-Path $libffiInstallDir) { Remove-Item $libffiInstallDir -Recurse -Force }
|
||||
$innerDir = Get-ChildItem $extractTemp -Directory | Select-Object -First 1
|
||||
if ($innerDir) {
|
||||
Move-Item $innerDir.FullName $libffiInstallDir
|
||||
} else {
|
||||
# Flat zip — use the extract root directly
|
||||
Move-Item $extractTemp $libffiInstallDir
|
||||
}
|
||||
|
||||
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $extractTemp) { Remove-Item $extractTemp -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
Write-Host "libffi $libffiVersion installed to $libffiInstallDir" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "libffi already installed at $libffiInstallDir" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- Parse Python version from pyproject.toml ---
|
||||
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
|
||||
$repoRoot = Resolve-Path (Join-Path $scriptDir "..\..\..")
|
||||
$pyprojectPath = Join-Path $repoRoot "pyproject.toml"
|
||||
if (-not (Test-Path $pyprojectPath)) {
|
||||
Write-Host "ERROR: pyproject.toml not found at $pyprojectPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$pyproject = Get-Content $pyprojectPath -Raw
|
||||
$pyverMatch = $pyproject | Select-String -Pattern 'requires-python\s*=\s*"([^"]+)"' | ForEach-Object { $_.Matches[0].Groups[1].Value }
|
||||
if (-not $pyverMatch) {
|
||||
Write-Host "ERROR: Could not find requires-python in pyproject.toml" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
# Use the highest version in the range (e.g. ">=3.13, <3.15" → 3.14.x)
|
||||
$pyver = $pyverMatch -replace '[^0-9.,<>= ]', ''
|
||||
$maxVer = ($pyver -split ',') | Where-Object { $_ -match '<' } | ForEach-Object { $_ -replace '[^0-9.]', '' } | Sort-Object -Descending | Select-Object -First 1
|
||||
if ($maxVer) {
|
||||
$major, $minor = $maxVer -split '\.'
|
||||
$targetMinor = [int]$minor - 1
|
||||
$targetVer = "$major.$targetMinor.x"
|
||||
} else {
|
||||
$targetVer = $pyver -replace '[^0-9.]', ''
|
||||
}
|
||||
Write-Host "Target Python version: $targetVer" -ForegroundColor Cyan
|
||||
|
||||
# --- Install and set global Python version ---
|
||||
$pyenv = "pyenv"
|
||||
if (-not (Get-Command $pyenv -ErrorAction SilentlyContinue)) {
|
||||
$pyenv = "$pyenvBin"
|
||||
}
|
||||
|
||||
$versionPrefix = $targetVer -replace '\.x$', ''
|
||||
$installedVersions = (& $pyenv versions 2>&1) | Out-String
|
||||
if ($installedVersions -notmatch [regex]::Escape($versionPrefix)) {
|
||||
Write-Host "Installing Python $targetVer via pyenv-win..." -ForegroundColor Yellow
|
||||
& $pyenv install $targetVer -q
|
||||
} else {
|
||||
Write-Host "Python $targetVer already installed via pyenv." -ForegroundColor Green
|
||||
}
|
||||
|
||||
$currentGlobal = ((& $pyenv global 2>&1) | Out-String).Trim()
|
||||
if ($currentGlobal -notmatch [regex]::Escape($versionPrefix)) {
|
||||
& $pyenv global $targetVer
|
||||
Write-Host "pyenv-win global version set to $targetVer" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "pyenv-win global already set to $currentGlobal" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "=== Bootstrap complete ===" -ForegroundColor Cyan
|
||||
@@ -1,10 +1,13 @@
|
||||
import os
|
||||
block_cipher = None
|
||||
SPEC_DIR = os.path.dirname(os.path.abspath(SPEC))
|
||||
SRC_DIR = os.path.normpath(os.path.join(SPEC_DIR, '..', '..', 'src'))
|
||||
a = Analysis(
|
||||
['../../src/main.py'],
|
||||
pathex=['src'],
|
||||
[os.path.join(SRC_DIR, 'main.py')],
|
||||
pathex=[SRC_DIR],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('../../src/gui', 'gui'),
|
||||
(os.path.join(SRC_DIR, 'gui'), 'gui'),
|
||||
],
|
||||
hiddenimports=[
|
||||
# GUI modules
|
||||
@@ -33,7 +36,8 @@ a = Analysis(
|
||||
'managers.transfer_manager',
|
||||
# Utility modules
|
||||
'utils',
|
||||
'utils.file_deduplication'
|
||||
'utils.file_deduplication',
|
||||
'utils.security_utils'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import os
|
||||
block_cipher = None
|
||||
SPEC_DIR = os.path.dirname(os.path.abspath(SPEC))
|
||||
SRC_DIR = os.path.normpath(os.path.join(SPEC_DIR, '..', '..', 'src'))
|
||||
a = Analysis(
|
||||
['../../src/main.py'],
|
||||
pathex=['src'],
|
||||
[os.path.join(SRC_DIR, 'main.py')],
|
||||
pathex=[SRC_DIR],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('../../src/gui', 'gui'),
|
||||
('../../scripts/debian_postinst.sh', 'scripts'),
|
||||
(os.path.join(SRC_DIR, 'gui'), 'gui'),
|
||||
(os.path.join(SPEC_DIR, '..', 'debian_postinst.sh'), 'scripts'),
|
||||
],
|
||||
hiddenimports=[
|
||||
# GUI modules
|
||||
@@ -35,7 +38,8 @@ a = Analysis(
|
||||
'managers.transfer_manager',
|
||||
# Utility modules
|
||||
'utils',
|
||||
'utils.file_deduplication'
|
||||
'utils.file_deduplication',
|
||||
'utils.security_utils'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
block_cipher = None
|
||||
SPEC_DIR = os.path.dirname(os.path.abspath(SPEC))
|
||||
SRC_DIR = os.path.normpath(os.path.join(SPEC_DIR, '..', '..', 'src'))
|
||||
a = Analysis(
|
||||
['../../src/main.py'],
|
||||
pathex=['src'],
|
||||
[os.path.join(SRC_DIR, 'main.py')],
|
||||
pathex=[SRC_DIR],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('../../src/gui', 'gui'),
|
||||
('../../scripts/rhel_postinst.sh', 'scripts'),
|
||||
(os.path.join(SRC_DIR, 'gui'), 'gui'),
|
||||
(os.path.join(SPEC_DIR, '..', 'rhel_postinst.sh'), 'scripts'),
|
||||
],
|
||||
hiddenimports=[
|
||||
# GUI modules
|
||||
@@ -34,7 +37,8 @@ a = Analysis(
|
||||
'managers.transfer_manager',
|
||||
# Utility modules
|
||||
'utils',
|
||||
'utils.file_deduplication'
|
||||
'utils.file_deduplication',
|
||||
'utils.security_utils'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import os
|
||||
block_cipher = None
|
||||
SPEC_DIR = os.path.dirname(os.path.abspath(SPEC))
|
||||
SRC_DIR = os.path.normpath(os.path.join(SPEC_DIR, '..', '..', 'src'))
|
||||
a = Analysis(
|
||||
['../../src/main.py'],
|
||||
pathex=['../..'],
|
||||
[os.path.join(SRC_DIR, 'main.py')],
|
||||
pathex=[SRC_DIR],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('../../src/gui', 'gui'),
|
||||
(os.path.join(SRC_DIR, 'gui'), 'gui'),
|
||||
],
|
||||
hiddenimports=[
|
||||
# GUI modules
|
||||
@@ -34,7 +37,8 @@ a = Analysis(
|
||||
'managers.transfer_manager',
|
||||
# Utility modules
|
||||
'utils',
|
||||
'utils.file_deduplication'
|
||||
'utils.file_deduplication',
|
||||
'utils.security_utils'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
@@ -66,5 +70,5 @@ exe = EXE(
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon='../../icon_media/robot_files_256.ico',
|
||||
icon=os.path.join(SPEC_DIR, '..', '..', 'icon_media', 'robot_files_256.ico'),
|
||||
)
|
||||
@@ -0,0 +1,298 @@
|
||||
# run_vagrant_tests.ps1 — Host-side orchestrator for Vagrant Windows 11 test cycle.
|
||||
#
|
||||
# Workflow:
|
||||
# 1. Build the installer locally (PyInstaller + Inno Setup)
|
||||
# 2. Ensure the Vagrant VM is running and has a "clean" snapshot
|
||||
# 3. Restore to the clean snapshot
|
||||
# 4. Push the installer to the VM
|
||||
# 5. Run the test script inside the VM via WinRM
|
||||
# 6. Retrieve screenshots and results to the host
|
||||
# 7. Print results summary to CLI
|
||||
# 8. Revert VM back to clean snapshot
|
||||
#
|
||||
# Usage (from repo root):
|
||||
# .\scripts\windows\run_vagrant_tests.ps1
|
||||
# .\scripts\windows\run_vagrant_tests.ps1 -SkipBuild
|
||||
# .\scripts\windows\run_vagrant_tests.ps1 -SkipRevert
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Vagrant and Hyper-V enabled on the host
|
||||
# - Inno Setup 6 installed (default path or in PATH)
|
||||
# - Poetry environment set up
|
||||
# - The VM created and "clean" snapshot saved:
|
||||
# cd vagrant && vagrant up --provider=hyperv && vagrant snapshot save clean
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$SkipBuild,
|
||||
[switch]$SkipRevert,
|
||||
[string]$SnapshotName = "clean",
|
||||
[string]$LocalResultsDir = ""
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
||||
$VagrantDir = Join-Path $RepoRoot "vagrant"
|
||||
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
|
||||
if (-not $LocalResultsDir) {
|
||||
$LocalResultsDir = Join-Path $RepoRoot "vagrant_test_results\$Timestamp"
|
||||
}
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "`n>>> $Message" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
# ── Ensure we're in the vagrant directory ────────────────────────────────────
|
||||
|
||||
Push-Location $VagrantDir
|
||||
try {
|
||||
|
||||
# ── Step 1: Build the installer locally ──────────────────────────────────
|
||||
|
||||
$installerPath = Join-Path $RepoRoot "dist\android-file-handler-setup.exe"
|
||||
|
||||
if ($SkipBuild) {
|
||||
Write-Step "Skipping build (-SkipBuild specified)"
|
||||
if (-not (Test-Path $installerPath)) {
|
||||
Write-Host "ERROR: -SkipBuild was specified but installer not found at $installerPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Step "Building installer locally (PyInstaller + Inno Setup)"
|
||||
|
||||
# PyInstaller — compile the exe
|
||||
Push-Location $RepoRoot
|
||||
try {
|
||||
Write-Host " Running PyInstaller..." -ForegroundColor Yellow
|
||||
poetry run pyinstaller scripts\spec_scripts\android-file-handler-windows.spec --noconfirm
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "ERROR: PyInstaller failed with exit code $LASTEXITCODE" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$pyinstallerExe = Join-Path $RepoRoot "dist\android-file-handler-windows.exe"
|
||||
if (-not (Test-Path $pyinstallerExe)) {
|
||||
Write-Host "ERROR: PyInstaller output not found at $pyinstallerExe" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host " PyInstaller output: $pyinstallerExe" -ForegroundColor Green
|
||||
|
||||
# Inno Setup — compile the installer
|
||||
Write-Host " Running Inno Setup..." -ForegroundColor Yellow
|
||||
$isccPaths = @(
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
|
||||
"C:\Program Files\Inno Setup 6\ISCC.exe"
|
||||
)
|
||||
$iscc = $isccPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if (-not $iscc) {
|
||||
$iscc = Get-Command ISCC.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
|
||||
}
|
||||
if (-not $iscc) {
|
||||
Write-Host "ERROR: Inno Setup (ISCC.exe) not found. Install Inno Setup 6 or add it to PATH." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
& $iscc "scripts\windows\android-file-handler-setup.iss"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "ERROR: Inno Setup failed with exit code $LASTEXITCODE" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $installerPath)) {
|
||||
Write-Host "ERROR: Installer not found at $installerPath after Inno Setup" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host " Installer built: $installerPath" -ForegroundColor Green
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# ── Step 2: Verify VM and snapshot exist ─────────────────────────────────
|
||||
|
||||
Write-Step "Checking Vagrant VM status"
|
||||
$status = vagrant status --machine-readable 2>&1 |
|
||||
Select-String "state,running" -Quiet
|
||||
|
||||
if (-not $status) {
|
||||
Write-Host "VM is not running. Starting it..." -ForegroundColor Yellow
|
||||
vagrant up --provider=hyperv
|
||||
}
|
||||
|
||||
Write-Step "Verifying '$SnapshotName' snapshot exists"
|
||||
$snapshots = vagrant snapshot list 2>&1
|
||||
if ($snapshots -notmatch [regex]::Escape($SnapshotName)) {
|
||||
Write-Host "Snapshot '$SnapshotName' not found. Creating it now..." -ForegroundColor Yellow
|
||||
vagrant snapshot save $SnapshotName
|
||||
}
|
||||
|
||||
# ── Step 3: Restore to clean snapshot ────────────────────────────────────
|
||||
|
||||
Write-Step "Restoring VM to '$SnapshotName' snapshot"
|
||||
vagrant snapshot restore $SnapshotName
|
||||
|
||||
# ── Step 4: Push installer to the VM ─────────────────────────────────────
|
||||
|
||||
Write-Step "Pushing installer to the VM"
|
||||
|
||||
$remoteInstallerDir = "C:\vagrant_installer"
|
||||
$remoteInstallerPath = "$remoteInstallerDir\android-file-handler-setup.exe"
|
||||
|
||||
vagrant winrm --shell powershell --command `
|
||||
"New-Item -ItemType Directory -Path '$remoteInstallerDir' -Force | Out-Null"
|
||||
|
||||
# Base64-encode the installer, transfer via WinRM, decode on the VM
|
||||
$installerBytes = [IO.File]::ReadAllBytes($installerPath)
|
||||
$installerSizeMB = [math]::Round($installerBytes.Length / 1MB, 1)
|
||||
Write-Host " Installer size: ${installerSizeMB} MB — transferring via WinRM..." -ForegroundColor Yellow
|
||||
|
||||
# Split into chunks to avoid WinRM message size limits (< 2MB per command)
|
||||
$chunkSize = 1 * 1024 * 1024 # 1 MB chunks
|
||||
$totalChunks = [math]::Ceiling($installerBytes.Length / $chunkSize)
|
||||
|
||||
vagrant winrm --shell powershell --command `
|
||||
"if (Test-Path '$remoteInstallerPath') { Remove-Item '$remoteInstallerPath' -Force }"
|
||||
|
||||
for ($idx = 0; $idx -lt $totalChunks; $idx++) {
|
||||
$offset = $idx * $chunkSize
|
||||
$length = [math]::Min($chunkSize, $installerBytes.Length - $offset)
|
||||
$chunk = New-Object byte[] $length
|
||||
[Array]::Copy($installerBytes, $offset, $chunk, 0, $length)
|
||||
$b64Chunk = [Convert]::ToBase64String($chunk)
|
||||
|
||||
$appendScript = if ($idx -eq 0) {
|
||||
"[IO.File]::WriteAllBytes('$remoteInstallerPath', [Convert]::FromBase64String('$b64Chunk'))"
|
||||
}
|
||||
else {
|
||||
"`$bytes = [Convert]::FromBase64String('$b64Chunk'); " +
|
||||
"`$stream = [IO.File]::Open('$remoteInstallerPath', 'Append'); " +
|
||||
"`$stream.Write(`$bytes, 0, `$bytes.Length); `$stream.Close()"
|
||||
}
|
||||
vagrant winrm --shell powershell --command $appendScript
|
||||
Write-Host " Chunk $($idx + 1)/$totalChunks transferred" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# Verify transfer
|
||||
$remoteSize = vagrant winrm --shell powershell --command `
|
||||
"(Get-Item '$remoteInstallerPath').Length"
|
||||
Write-Host " Remote file size: $($remoteSize.Trim()) bytes (expected: $($installerBytes.Length))" -ForegroundColor DarkGray
|
||||
|
||||
# ── Step 5: Run tests inside the VM ──────────────────────────────────────
|
||||
|
||||
Write-Step "Running test suite inside the VM"
|
||||
|
||||
$testExitCode = 0
|
||||
try {
|
||||
vagrant winrm --shell powershell --command `
|
||||
"Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force; & C:\vagrant_project\vagrant\test_installer.ps1 -InstallerPath 'C:\vagrant_installer\android-file-handler-setup.exe'"
|
||||
}
|
||||
catch {
|
||||
$testExitCode = 1
|
||||
Write-Host "Test script returned failures." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ── Step 6: Retrieve results and screenshots ─────────────────────────────
|
||||
|
||||
Write-Step "Retrieving test results and screenshots"
|
||||
|
||||
if (-not (Test-Path $LocalResultsDir)) {
|
||||
New-Item -ItemType Directory -Path $LocalResultsDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Copy results from shared folder on VM via vagrant scp or direct file copy
|
||||
# The test writes to C:\vagrant_test_results inside the VM.
|
||||
# We retrieve via WinRM by reading files.
|
||||
try {
|
||||
# Get list of result files
|
||||
$remoteFiles = vagrant winrm --shell powershell --command `
|
||||
"if (Test-Path 'C:\vagrant_test_results') { Get-ChildItem 'C:\vagrant_test_results' -Name }" 2>&1 |
|
||||
Where-Object { $_ -is [string] -and $_.Trim() -ne "" }
|
||||
|
||||
foreach ($fileName in $remoteFiles) {
|
||||
$trimmedName = $fileName.Trim()
|
||||
if (-not $trimmedName) { continue }
|
||||
|
||||
$remotePath = "C:\vagrant_test_results\$trimmedName"
|
||||
$localPath = Join-Path $LocalResultsDir $trimmedName
|
||||
|
||||
if ($trimmedName -match '\.csv$') {
|
||||
# Text file — read content and save
|
||||
$content = vagrant winrm --shell powershell --command `
|
||||
"Get-Content -Path '$remotePath' -Raw"
|
||||
$content | Out-File -FilePath $localPath -Encoding utf8
|
||||
Write-Host " Retrieved: $trimmedName" -ForegroundColor DarkGray
|
||||
}
|
||||
elseif ($trimmedName -match '\.(png|jpg|bmp)$') {
|
||||
# Binary file — base64 encode, transfer, decode
|
||||
$b64 = vagrant winrm --shell powershell --command `
|
||||
"[Convert]::ToBase64String([IO.File]::ReadAllBytes('$remotePath'))"
|
||||
[IO.File]::WriteAllBytes($localPath, [Convert]::FromBase64String($b64.Trim()))
|
||||
Write-Host " Retrieved: $trimmedName" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " Warning: Could not retrieve some result files: $_" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ── Step 7: Print local summary ──────────────────────────────────────────
|
||||
|
||||
Write-Step "Test Results"
|
||||
|
||||
$csvPath = Join-Path $LocalResultsDir "test_results.csv"
|
||||
if (Test-Path $csvPath) {
|
||||
$results = Import-Csv $csvPath
|
||||
$results | Format-Table -AutoSize
|
||||
|
||||
$passCount = ($results | Where-Object { $_.Status -eq "PASS" }).Count
|
||||
$failCount = ($results | Where-Object { $_.Status -eq "FAIL" }).Count
|
||||
Write-Host " Passed: $passCount | Failed: $failCount | Total: $($passCount + $failCount)" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
|
||||
}
|
||||
else {
|
||||
Write-Host " No results CSV found. Check VM output above." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`n Results directory: $LocalResultsDir" -ForegroundColor White
|
||||
|
||||
# List screenshots
|
||||
$screenshots = Get-ChildItem $LocalResultsDir -Filter "*.png" -ErrorAction SilentlyContinue
|
||||
if ($screenshots) {
|
||||
Write-Host " Screenshots:" -ForegroundColor White
|
||||
foreach ($shot in $screenshots) {
|
||||
Write-Host " - $($shot.FullName)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
# ── Step 8: Revert to clean snapshot ─────────────────────────────────────
|
||||
|
||||
if (-not $SkipRevert) {
|
||||
Write-Step "Reverting VM to '$SnapshotName' snapshot"
|
||||
vagrant snapshot restore $SnapshotName
|
||||
Write-Host "VM reverted to clean state." -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "`n -SkipRevert specified, VM left in current state." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ── Exit ─────────────────────────────────────────────────────────────────
|
||||
|
||||
Write-Host ""
|
||||
if ($testExitCode -ne 0) {
|
||||
Write-Host "One or more tests FAILED." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
else {
|
||||
Write-Host "All tests PASSED." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# Vagrant Windows 11 Test Environment
|
||||
|
||||
Automated testing of the Android File Handler installer on a clean Windows 11 VM using Hyper-V.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Vagrant](https://www.vagrantup.com/) >= 2.4
|
||||
- Hyper-V enabled (`Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All`)
|
||||
- [Inno Setup 6](https://jrsoftware.org/isdl.php) installed (default path or ISCC.exe in PATH)
|
||||
- Poetry environment set up (`poetry install`)
|
||||
|
||||
## Setup (one-time)
|
||||
|
||||
```powershell
|
||||
cd vagrant
|
||||
vagrant up --provider=hyperv
|
||||
vagrant snapshot save clean
|
||||
```
|
||||
|
||||
> **Note:** Vagrant will prompt for your Windows credentials to set up the SMB synced folder.
|
||||
> You can set `VAGRANT_SMB_USERNAME` and `VAGRANT_SMB_PASSWORD` environment variables to skip the prompt.
|
||||
|
||||
## Running Tests
|
||||
|
||||
From the repo root:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\run_vagrant_tests.ps1
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Build the installer locally (PyInstaller + Inno Setup)
|
||||
2. Restore the VM to the `clean` snapshot
|
||||
3. Push the installer to the VM via WinRM
|
||||
4. Run the installer silently inside the VM
|
||||
5. Verify installation (directory, exe, shortcuts, registry)
|
||||
6. Launch the app, find the window, take a GUI screenshot
|
||||
7. Run silent uninstall and verify cleanup
|
||||
8. Retrieve results CSV + screenshots to `vagrant_test_results\<timestamp>\`
|
||||
9. Print a summary table to the CLI
|
||||
10. Revert the VM back to the `clean` snapshot
|
||||
|
||||
### Options
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-SkipBuild` | Skip the local build step (use an existing installer in `dist\`) |
|
||||
| `-SkipRevert` | Leave the VM in post-test state (useful for debugging) |
|
||||
| `-SnapshotName <name>` | Use a different snapshot name (default: `clean`) |
|
||||
| `-LocalResultsDir <path>` | Override where results are saved |
|
||||
|
||||
## What Gets Tested
|
||||
|
||||
| # | Test | Description |
|
||||
|---|---|---|
|
||||
| 1 | Installer exists | Checks the built `.exe` is present |
|
||||
| 2 | Silent install | Runs the Inno Setup installer with `/VERYSILENT` |
|
||||
| 3 | Install directory | Verifies `C:\Program Files\Android File Handler` exists |
|
||||
| 4 | Executable present | Checks the app `.exe` is in the install directory |
|
||||
| 5 | Start Menu shortcut | Verifies the Start Menu entry was created |
|
||||
| 6 | Registry entry | Checks Add/Remove Programs registration |
|
||||
| 7 | App launches | Starts the app and confirms it stays running |
|
||||
| 8 | Window found | Finds the main window by its title |
|
||||
| 9 | Silent uninstall | Runs the uninstaller and verifies cleanup |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
vagrant/
|
||||
├── Vagrantfile # VM definition (Windows 11 Enterprise, Hyper-V)
|
||||
├── provision.ps1 # VM provisioning (nircmd for screenshots)
|
||||
├── test_installer.ps1 # Test script that runs inside the VM
|
||||
└── README.md # This file
|
||||
scripts/windows/
|
||||
└── run_vagrant_tests.ps1 # Host-side orchestrator (builds, pushes, tests, reverts)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**VM won't start**: Ensure Hyper-V is enabled and you are running the terminal as Administrator.
|
||||
|
||||
**WinRM connection fails**: The box uses plaintext WinRM on port 55985. Ensure no firewall blocks it.
|
||||
|
||||
**SMB share prompt**: Set `VAGRANT_SMB_USERNAME` and `VAGRANT_SMB_PASSWORD` environment variables to avoid interactive credential prompts.
|
||||
|
||||
**Inno Setup not found**: Install Inno Setup 6 to the default path or add `ISCC.exe` to your PATH.
|
||||
|
||||
**Want to inspect the VM manually**: Use `vagrant rdp` or pass `-SkipRevert`.
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
#
|
||||
# Vagrant Windows 11 test machine for Android File Handler installer testing.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Vagrant >= 2.4
|
||||
# - Hyper-V enabled (Windows host)
|
||||
#
|
||||
# Usage:
|
||||
# cd vagrant
|
||||
# vagrant up --provider=hyperv
|
||||
# vagrant snapshot save clean
|
||||
# ..\scripts\windows\run_vagrant_tests.ps1
|
||||
#
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "gusztavvargadr/windows-11-24h2-enterprise"
|
||||
|
||||
config.vm.hostname = "afh-test"
|
||||
config.vm.communicator = "winrm"
|
||||
|
||||
config.winrm.username = "vagrant"
|
||||
config.winrm.password = "vagrant"
|
||||
config.winrm.transport = :plaintext
|
||||
config.winrm.basic_auth_only = true
|
||||
|
||||
config.vm.guest = :windows
|
||||
config.vm.boot_timeout = 900
|
||||
config.vm.graceful_halt_timeout = 120
|
||||
|
||||
config.vm.network "forwarded_port", guest: 5985, host: 55985
|
||||
|
||||
config.vm.provider "hyperv" do |hv|
|
||||
hv.vmname = "afh-win11-test"
|
||||
hv.memory = 4096
|
||||
hv.maxmemory = 4096
|
||||
hv.cpus = 2
|
||||
hv.enable_enhanced_session_mode = true
|
||||
hv.linked_clone = true
|
||||
end
|
||||
|
||||
# SMB synced folder — Hyper-V does not support VirtualBox shared folders.
|
||||
# Vagrant will prompt for host credentials on first `vagrant up` to set up the SMB share.
|
||||
config.vm.synced_folder "..", "/vagrant_project",
|
||||
type: "smb",
|
||||
smb_username: ENV["VAGRANT_SMB_USERNAME"],
|
||||
smb_password: ENV["VAGRANT_SMB_PASSWORD"],
|
||||
mount_options: ["ro"]
|
||||
|
||||
config.vm.provision "shell",
|
||||
path: "provision.ps1",
|
||||
privileged: true
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# provision.ps1 — Vagrant provisioning script for Windows 11 test VM.
|
||||
# Installs prerequisites needed to test the Android File Handler installer.
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "=== Provisioning Android File Handler test VM ===" -ForegroundColor Cyan
|
||||
|
||||
# --- Enable auto-logon so GUI tests work after reboot ---
|
||||
$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
|
||||
Set-ItemProperty -Path $RegPath -Name AutoAdminLogon -Value "1"
|
||||
Set-ItemProperty -Path $RegPath -Name DefaultUserName -Value "vagrant"
|
||||
Set-ItemProperty -Path $RegPath -Name DefaultPassword -Value "vagrant"
|
||||
|
||||
# --- Install Chocolatey (package manager) ---
|
||||
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing Chocolatey..." -ForegroundColor Yellow
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
}
|
||||
|
||||
# --- Install nircmd for screenshot capture ---
|
||||
Write-Host "Installing nircmd for screenshot capture..." -ForegroundColor Yellow
|
||||
choco install nircmd -y --no-progress
|
||||
|
||||
# Refresh PATH
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
|
||||
Write-Host "=== Provisioning complete ===" -ForegroundColor Green
|
||||
@@ -0,0 +1,201 @@
|
||||
# test_installer.ps1 — Runs INSIDE the Vagrant VM via WinRM.
|
||||
# Tests installation and basic function of Android File Handler.
|
||||
#
|
||||
# Exit codes: 0 = all passed, 1 = one or more failures.
|
||||
|
||||
param(
|
||||
[string]$InstallerPath = "C:\vagrant_project\dist\android-file-handler-setup.exe",
|
||||
[string]$ScreenshotDir = "C:\vagrant_test_results"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
$Script:Passed = 0
|
||||
$Script:Failed = 0
|
||||
$Script:Results = @()
|
||||
|
||||
function Write-TestResult {
|
||||
param([string]$Name, [bool]$Pass, [string]$Detail = "")
|
||||
$status = if ($Pass) { "PASS" } else { "FAIL" }
|
||||
$color = if ($Pass) { "Green" } else { "Red" }
|
||||
$msg = "[$status] $Name"
|
||||
if ($Detail) { $msg += " — $Detail" }
|
||||
Write-Host $msg -ForegroundColor $color
|
||||
if ($Pass) { $Script:Passed++ } else { $Script:Failed++ }
|
||||
$Script:Results += [PSCustomObject]@{ Test = $Name; Status = $status; Detail = $Detail }
|
||||
}
|
||||
|
||||
function Take-Screenshot {
|
||||
param([string]$Name)
|
||||
if (-not (Test-Path $ScreenshotDir)) {
|
||||
New-Item -ItemType Directory -Path $ScreenshotDir -Force | Out-Null
|
||||
}
|
||||
$outFile = Join-Path $ScreenshotDir "$Name.png"
|
||||
$nircmd = Get-Command nircmd -ErrorAction SilentlyContinue
|
||||
if ($nircmd) {
|
||||
Start-Process -FilePath $nircmd.Source -ArgumentList "savescreenshot", $outFile -NoNewWindow -Wait
|
||||
return $outFile
|
||||
}
|
||||
# Fallback: .NET screenshot
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
||||
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||
$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
|
||||
$bitmap.Save($outFile, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$graphics.Dispose()
|
||||
$bitmap.Dispose()
|
||||
return $outFile
|
||||
}
|
||||
|
||||
# ── Setup ────────────────────────────────────────────────────────────────────
|
||||
|
||||
$AppName = "Android File Handler"
|
||||
$ExeName = "android-file-handler-windows.exe"
|
||||
$DefaultInstDir = Join-Path $env:ProgramFiles $AppName
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=============================================" -ForegroundColor Cyan
|
||||
Write-Host " Android File Handler — Vagrant Test Suite " -ForegroundColor Cyan
|
||||
Write-Host "=============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if (-not (Test-Path $ScreenshotDir)) {
|
||||
New-Item -ItemType Directory -Path $ScreenshotDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# ── Test 1: Installer exists ────────────────────────────────────────────────
|
||||
|
||||
$installerExists = Test-Path $InstallerPath
|
||||
Write-TestResult -Name "Installer file exists" -Pass $installerExists -Detail $InstallerPath
|
||||
|
||||
if (-not $installerExists) {
|
||||
Write-Host "`nFATAL: Installer not found at $InstallerPath. Build the installer first." -ForegroundColor Red
|
||||
Write-Host " poetry run pyinstaller scripts\spec_scripts\android-file-handler-windows.spec"
|
||||
Write-Host " iscc scripts\windows\android-file-handler-setup.iss"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Test 2: Silent install ──────────────────────────────────────────────────
|
||||
|
||||
Write-Host "`n--- Running silent install ---" -ForegroundColor Yellow
|
||||
$installProc = Start-Process -FilePath $InstallerPath `
|
||||
-ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/SP-" `
|
||||
-Wait -PassThru
|
||||
|
||||
$installOk = $installProc.ExitCode -eq 0
|
||||
Write-TestResult -Name "Silent install completed" -Pass $installOk -Detail "Exit code: $($installProc.ExitCode)"
|
||||
|
||||
# ── Test 3: Install directory created ────────────────────────────────────────
|
||||
|
||||
$dirExists = Test-Path $DefaultInstDir
|
||||
Write-TestResult -Name "Install directory exists" -Pass $dirExists -Detail $DefaultInstDir
|
||||
|
||||
# ── Test 4: Executable present ──────────────────────────────────────────────
|
||||
|
||||
$exePath = Join-Path $DefaultInstDir $ExeName
|
||||
$exeExists = Test-Path $exePath
|
||||
Write-TestResult -Name "Executable exists" -Pass $exeExists -Detail $exePath
|
||||
|
||||
# ── Test 5: Start Menu shortcut ─────────────────────────────────────────────
|
||||
|
||||
$startMenuDir = Join-Path $env:ProgramData "Microsoft\Windows\Start Menu\Programs\$AppName"
|
||||
$shortcutPath = Join-Path $startMenuDir "$AppName.lnk"
|
||||
$shortcutExists = Test-Path $shortcutPath
|
||||
Write-TestResult -Name "Start Menu shortcut exists" -Pass $shortcutExists -Detail $shortcutPath
|
||||
|
||||
# ── Test 6: Registry uninstall entry ────────────────────────────────────────
|
||||
|
||||
$uninstallKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
|
||||
$registryEntry = Get-ChildItem $uninstallKey -ErrorAction SilentlyContinue |
|
||||
Where-Object { (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).DisplayName -eq $AppName }
|
||||
$registryOk = $null -ne $registryEntry
|
||||
Write-TestResult -Name "Registry uninstall entry" -Pass $registryOk
|
||||
|
||||
# ── Test 7: Launch application & capture GUI ─────────────────────────────────
|
||||
|
||||
if ($exeExists) {
|
||||
Write-Host "`n--- Launching application ---" -ForegroundColor Yellow
|
||||
|
||||
$appProc = Start-Process -FilePath $exePath -PassThru
|
||||
Start-Sleep -Seconds 8
|
||||
|
||||
# Check if process is still running (GUI app should stay alive)
|
||||
$appRunning = -not $appProc.HasExited
|
||||
Write-TestResult -Name "Application launches and stays running" -Pass $appRunning
|
||||
|
||||
# Look for the window by title
|
||||
Add-Type @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public class Win32 {
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
}
|
||||
"@
|
||||
$hwnd = [Win32]::FindWindow($null, $AppName)
|
||||
$windowFound = $hwnd -ne [IntPtr]::Zero
|
||||
Write-TestResult -Name "Main window found by title" -Pass $windowFound -Detail "HWND: $hwnd"
|
||||
|
||||
# Take screenshot of the running application
|
||||
Start-Sleep -Seconds 2
|
||||
$screenshotFile = Take-Screenshot -Name "app_running"
|
||||
Write-Host " Screenshot saved: $screenshotFile" -ForegroundColor DarkGray
|
||||
|
||||
# Gracefully close
|
||||
if (-not $appProc.HasExited) {
|
||||
$appProc.CloseMainWindow() | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
if (-not $appProc.HasExited) {
|
||||
$appProc.Kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-TestResult -Name "Application launches and stays running" -Pass $false -Detail "Skipped — exe not found"
|
||||
Write-TestResult -Name "Main window found by title" -Pass $false -Detail "Skipped — exe not found"
|
||||
}
|
||||
|
||||
# ── Test 8: Silent uninstall ────────────────────────────────────────────────
|
||||
|
||||
Write-Host "`n--- Running silent uninstall ---" -ForegroundColor Yellow
|
||||
$uninstallerPath = Join-Path $DefaultInstDir "unins000.exe"
|
||||
if (Test-Path $uninstallerPath) {
|
||||
$uninstProc = Start-Process -FilePath $uninstallerPath `
|
||||
-ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART" `
|
||||
-Wait -PassThru
|
||||
$uninstallOk = $uninstProc.ExitCode -eq 0
|
||||
Write-TestResult -Name "Silent uninstall completed" -Pass $uninstallOk -Detail "Exit code: $($uninstProc.ExitCode)"
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
$dirGone = -not (Test-Path $DefaultInstDir)
|
||||
Write-TestResult -Name "Install directory removed after uninstall" -Pass $dirGone
|
||||
}
|
||||
else {
|
||||
Write-TestResult -Name "Silent uninstall completed" -Pass $false -Detail "Uninstaller not found"
|
||||
Write-TestResult -Name "Install directory removed after uninstall" -Pass $false -Detail "Skipped"
|
||||
}
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=============================================" -ForegroundColor Cyan
|
||||
Write-Host " Test Summary" -ForegroundColor Cyan
|
||||
Write-Host "=============================================" -ForegroundColor Cyan
|
||||
Write-Host " Passed : $Script:Passed" -ForegroundColor Green
|
||||
Write-Host " Failed : $Script:Failed" -ForegroundColor $(if ($Script:Failed -gt 0) { "Red" } else { "Green" })
|
||||
Write-Host " Total : $($Script:Passed + $Script:Failed)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$Script:Results | Format-Table -AutoSize
|
||||
|
||||
# Export results as CSV for retrieval
|
||||
$csvPath = Join-Path $ScreenshotDir "test_results.csv"
|
||||
$Script:Results | Export-Csv -Path $csvPath -NoTypeInformation
|
||||
Write-Host "Results saved to $csvPath" -ForegroundColor DarkGray
|
||||
|
||||
exit $(if ($Script:Failed -gt 0) { 1 } else { 0 })
|
||||
Reference in New Issue
Block a user