-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathpackage_plugin.py
More file actions
300 lines (256 loc) · 8.72 KB
/
package_plugin.py
File metadata and controls
300 lines (256 loc) · 8.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
import argparse
import os
import re
import zipfile
from datetime import datetime
from pathlib import Path
# Patterns to exclude from the zip
EXCLUDE_PATTERNS = [
# Generated UI files from Qt Designer
r"^ui_.*\.py$",
# Generated resource files
r"^resources_rc\.py$",
r"^.*_rc\.py$",
# Compiled Python files
r"^.*\.pyc$",
r"^.*\.pyo$",
# Backup files
r"^.*\.bak$",
r"^.*~$",
# IDE/editor files
r"^\..*\.swp$",
r"^.*\.orig$",
# Lock files
r"^uv\.lock$",
r"^poetry\.lock$",
r"^Pipfile\.lock$",
# Log files
r"^.*\.log$",
# Package script itself
r"^package_plugin\.py$",
]
# Directory names to exclude
EXCLUDE_DIRS = {
"__pycache__",
"__MACOSX",
".git",
".svn",
".hg",
".idea",
".vscode",
".pytest_cache",
".mypy_cache",
".tox",
".eggs",
"*.egg-info",
"build",
"dist",
"node_modules",
"help", # Generated help files (can be regenerated)
".venv", # Virtual environment
"venv",
"env",
"_extras", # Development/extras folder
"tests", # Unit tests (optional, include if you want)
"test",
}
def should_exclude_file(filename: str) -> bool:
"""Check if a file should be excluded based on its name."""
# Check against exclude patterns
for pattern in EXCLUDE_PATTERNS:
if re.match(pattern, filename):
return True
return False
def should_exclude_dir(dirname: str) -> bool:
"""Check if a directory should be excluded."""
# Exclude hidden directories (starting with .)
if dirname.startswith("."):
return True
# Check against exclude directory names
if dirname in EXCLUDE_DIRS:
return True
# Check for egg-info directories
if dirname.endswith(".egg-info"):
return True
return False
def get_version_from_metadata(plugin_dir: Path) -> str:
"""Extract version from metadata.txt file."""
metadata_file = plugin_dir / "metadata.txt"
if metadata_file.exists():
with open(metadata_file, "r", encoding="utf-8") as f:
for line in f:
if line.startswith("version="):
return line.split("=", 1)[1].strip()
return "unknown"
def package_plugin(
source_dir: Path,
output_path: Path | None = None,
target_name: str = "geo_agent",
include_version: bool = True,
) -> Path:
"""
Package the QGIS plugin into a zip file.
Args:
source_dir: Path to the geo_agent directory
output_path: Optional path for the output zip file
target_name: Name for the root folder in the zip (default: 'geo_agent')
include_version: Whether to include version in the zip filename
Returns:
Path to the created zip file
"""
if not source_dir.exists():
raise FileNotFoundError(f"Source directory not found: {source_dir}")
if not source_dir.is_dir():
raise ValueError(f"Source path is not a directory: {source_dir}")
# Get version from metadata
version = get_version_from_metadata(source_dir)
# Determine output path
if output_path is None:
if include_version:
zip_name = f"{target_name}-{version}.zip"
else:
zip_name = f"{target_name}.zip"
output_path = source_dir.parent / zip_name
# Ensure output directory exists
output_path.parent.mkdir(parents=True, exist_ok=True)
# Remove existing zip if it exists
if output_path.exists():
output_path.unlink()
print(f"Packaging plugin from: {source_dir}")
print(f"Output zip file: {output_path}")
print(f"Root folder name in zip: {target_name}")
print(f"Plugin version: {version}")
print()
files_added = 0
files_excluded = 0
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(source_dir):
# Filter out excluded directories (modify dirs in-place)
dirs[:] = [d for d in dirs if not should_exclude_dir(d)]
for file in files:
file_path = Path(root) / file
# Check if file should be excluded
if should_exclude_file(file):
print(f" Excluding: {file_path.relative_to(source_dir)}")
files_excluded += 1
continue
# Skip hidden files
if file.startswith("."):
print(f" Excluding hidden: {file_path.relative_to(source_dir)}")
files_excluded += 1
continue
# Calculate the archive name (rename root folder)
rel_path = file_path.relative_to(source_dir)
archive_name = Path(target_name) / rel_path
# Add file to zip
zipf.write(file_path, archive_name)
print(f" Adding: {archive_name}")
files_added += 1
print()
print(f"Package created successfully!")
print(f" Files added: {files_added}")
print(f" Files excluded: {files_excluded}")
print(f" Output: {output_path}")
print(f" Size: {output_path.stat().st_size / 1024:.1f} KB")
return output_path
def verify_zip(zip_path: Path) -> None:
"""Verify the contents of the created zip file."""
print()
print("Verifying zip contents:")
print("-" * 50)
with zipfile.ZipFile(zip_path, "r") as zipf:
# Check for any problematic entries
has_issues = False
for name in zipf.namelist():
# Check for unwanted patterns
basename = os.path.basename(name)
dirname = os.path.dirname(name)
if "__pycache__" in name:
print(f" WARNING: Found __pycache__: {name}")
has_issues = True
if "__MACOSX" in name:
print(f" WARNING: Found __MACOSX: {name}")
has_issues = True
if ".git" in name.split("/"):
print(f" WARNING: Found .git: {name}")
has_issues = True
if basename.startswith("ui_") and basename.endswith(".py"):
print(f" WARNING: Found ui_*.py: {name}")
has_issues = True
if basename == "resources_rc.py":
print(f" WARNING: Found resources_rc.py: {name}")
has_issues = True
if not has_issues:
print(" All checks passed!")
# List contents
print()
print("Zip contents:")
print("-" * 50)
for name in sorted(zipf.namelist()):
info = zipf.getinfo(name)
if not name.endswith("/"): # Skip directories
print(f" {name} ({info.file_size} bytes)")
def main():
parser = argparse.ArgumentParser(
description="Package GeoAgent QGIS plugin for repository upload",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"--output",
"-o",
type=Path,
default=None,
help="Output path for the zip file (default: geo_agent-{version}.zip in parent folder)",
)
parser.add_argument(
"--source",
"-s",
type=Path,
default=None,
help="Source directory (default: current directory where this script is located)",
)
parser.add_argument(
"--no-version",
action="store_true",
help="Don't include version in the zip filename",
)
parser.add_argument(
"--no-verify",
action="store_true",
help="Skip verification of the created zip",
)
args = parser.parse_args()
# Determine source directory - use the directory where this script is located
script_dir = Path(__file__).parent.resolve()
source_dir = args.source if args.source else script_dir
try:
# Create the package
zip_path = package_plugin(
source_dir=source_dir,
output_path=args.output,
target_name="geo_agent",
include_version=not args.no_version,
)
# Verify the package
if not args.no_verify:
verify_zip(zip_path)
print()
print("=" * 50)
print("Plugin packaged successfully!")
print(f"Upload this file to the QGIS plugin repository:")
print(f" {zip_path}")
print()
print("Next steps:")
print("1. Test the plugin by extracting the zip to:")
print(
f" {Path.home() / 'AppData' / 'Roaming' / 'QGIS' / 'QGIS3' / 'profiles' / 'default' / 'python' / 'plugins'}"
)
print("2. Restart QGIS and enable the plugin")
print("3. Upload to https://plugins.qgis.org/")
print("=" * 50)
except Exception as e:
print(f"Error: {e}")
raise SystemExit(1)
if __name__ == "__main__":
main()