-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild-linux.py
More file actions
406 lines (334 loc) · 14.2 KB
/
build-linux.py
File metadata and controls
406 lines (334 loc) · 14.2 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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/usr/bin/env python3
import os
import shutil
import subprocess
import tarfile
import zipfile
from pathlib import Path
import re
import xml.etree.ElementTree as ET
def get_version_input():
"""Get version number from user or use current version from .csproj"""
print("\n📦 Version Configuration")
print("=" * 50)
current_version = get_current_version()
if current_version:
print(f"Current version in .csproj: {current_version}")
version = input(f"Enter version number (e.g., 1.0.1) or press Enter to use current [{current_version or '1.0.0'}]: ").strip()
if not version:
version = current_version or "1.0.0"
if not re.match(r'^\d+\.\d+\.\d+$', version):
print(f"⚠️ Invalid version format. Using default: 1.0.0")
version = "1.0.0"
return version
def get_current_version():
"""Read current version from .csproj file"""
try:
tree = ET.parse("./AkademiTrack.csproj")
root = tree.getroot()
for prop_group in root.findall('.//PropertyGroup'):
version_elem = prop_group.find('Version')
if version_elem is not None and version_elem.text:
return version_elem.text.strip()
return None
except Exception as e:
print(f"⚠️ Could not read version from .csproj: {e}")
return None
def update_csproj_version(version):
"""Update version in .csproj file"""
try:
tree = ET.parse("./AkademiTrack.csproj")
root = tree.getroot()
version_updated = False
for prop_group in root.findall('.//PropertyGroup'):
version_elem = prop_group.find('Version')
if version_elem is not None:
version_elem.text = version
version_updated = True
break
if not version_updated:
prop_groups = root.findall('.//PropertyGroup')
if prop_groups:
version_elem = ET.SubElement(prop_groups[0], 'Version')
version_elem.text = version
version_updated = True
if version_updated:
tree.write("./AkademiTrack.csproj", encoding='utf-8', xml_declaration=True)
print(f"✅ Updated .csproj version to {version}")
return True
else:
print(f"⚠️ Could not update version in .csproj")
return False
except Exception as e:
print(f"❌ Failed to update .csproj: {e}")
return False
def create_desktop_file(version, install_dir):
"""Create .desktop file for Linux"""
desktop_content = f"""[Desktop Entry]
Version={version}
Type=Application
Name=AkademiTrack
Comment=Academic tracking and management application
Exec={install_dir}/AkademiTrack
Icon={install_dir}/akademitrack
Terminal=false
Categories=Education;Office;
StartupWMClass=AkademiTrack
"""
return desktop_content
def build_linux_release(version):
"""Build Linux release - creates portable tarball and standalone binary"""
print(f"\n🏗️ Building AkademiTrack for Linux (x64)...")
print("=" * 50)
# Directories
publish_dir = Path("./publish-linux")
publish_single = Path("./publish-linux-single")
release_folder = Path(f"./Releases/v{version}")
# Clean directories
if publish_dir.exists():
print(f"🧹 Cleaning publish directory...")
shutil.rmtree(publish_dir)
if publish_single.exists():
print(f"🧹 Cleaning single-file directory...")
shutil.rmtree(publish_single)
if release_folder.exists():
print(f"🧹 Cleaning release folder...")
shutil.rmtree(release_folder)
release_folder.mkdir(parents=True, exist_ok=True)
# Step 1: Publish for distribution (multi-file)
print(f"\n📦 Step 1: Publishing for distribution (multi-file)...")
publish_cmd = [
"dotnet", "publish",
"-c", "Release",
"--self-contained",
"-r", "linux-x64",
"-o", str(publish_dir),
"-p:PublishSingleFile=false"
]
print(f"Running: {' '.join(publish_cmd)}")
result = subprocess.run(publish_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"❌ Publish failed: {result.stderr}")
return False
print("✅ Published for distribution successfully")
# Step 2: Publish single file binary (for standalone distribution)
print(f"\n📦 Step 2: Publishing standalone single-file binary...")
publish_single_cmd = [
"dotnet", "publish",
"-c", "Release",
"--self-contained",
"-r", "linux-x64",
"-o", str(publish_single),
"-p:PublishSingleFile=true",
"-p:IncludeNativeLibrariesForSelfExtract=true",
"-p:EnableCompressionInSingleFile=true"
]
print(f"Running: {' '.join(publish_single_cmd)}")
result = subprocess.run(publish_single_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"❌ Single-file publish failed: {result.stderr}")
return False
print("✅ Published single-file binary successfully")
# Verify single-file executable exists
binary_single = publish_single / "AkademiTrack"
if not binary_single.exists():
print(f"❌ Single-file binary not found: {binary_single}")
return False
# Set executable permission
os.chmod(binary_single, 0o755)
binary_size = binary_single.stat().st_size / 1024 / 1024
print(f"✅ Single-file binary: {binary_single.name} ({binary_size:.1f} MB)")
if binary_size < 10:
print(f"⚠️ Warning: Binary seems too small ({binary_size:.1f} MB)")
# Step 3: Create portable tarball from multi-file build
print(f"\n📦 Step 3: Creating portable tarball...")
portable_tar = release_folder / f"AkademiTrack-linux-Portable.tar.gz"
try:
with tarfile.open(portable_tar, 'w:gz') as tar:
file_count = 0
for file_path in publish_dir.rglob('*'):
if file_path.is_file():
arc_name = f"AkademiTrack/{file_path.relative_to(publish_dir)}"
tar.add(file_path, arcname=arc_name)
file_count += 1
# Add desktop file to tarball
desktop_content = create_desktop_file(version, "/opt/akademitrack")
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.desktop', delete=False) as f:
f.write(desktop_content)
desktop_temp = f.name
tar.add(desktop_temp, arcname="AkademiTrack/akademitrack.desktop")
os.unlink(desktop_temp)
file_count += 1
print(f"✅ Added {file_count} files to portable tarball")
except Exception as e:
print(f"❌ Failed to create portable tarball: {e}")
return False
portable_size = portable_tar.stat().st_size / 1024 / 1024
print(f"✅ Portable tarball created: {portable_tar.name} ({portable_size:.1f} MB)")
# Step 4: Create Velopack release package
print(f"\n📦 Step 4: Creating Velopack release package...")
try:
# Create releases directory
releases_dir = Path("./Releases")
releases_dir.mkdir(exist_ok=True)
# Use vpk to pack the app
vpk_cmd = [
"vpk", "pack",
"--packId", "AkademiTrack",
"--packVersion", version,
"--packDir", str(publish_dir),
"--outputDir", str(releases_dir),
"--runtime", "linux-x64",
"--mainExe", "AkademiTrack"
]
# Add icon if it exists (look for PNG version for Linux)
icon_png = Path("./Assets/AT-1024.png")
if icon_png.exists():
vpk_cmd.extend(["--icon", str(icon_png)])
print(f"Running: {' '.join(vpk_cmd)}")
result = subprocess.run(vpk_cmd, capture_output=True, text=True)
if result.returncode == 0:
# Find the created release file
release_files = list(releases_dir.glob(f"AkademiTrack-{version}-*.nupkg"))
if release_files:
release_file = release_files[0]
velopack_size = release_file.stat().st_size / 1024 / 1024
print(f"✅ Velopack release created: {release_file.name} ({velopack_size:.1f} MB)")
# Copy to release folder
shutil.copy2(release_file, release_folder / release_file.name)
# Also copy RELEASES file if it exists
releases_file = releases_dir / "RELEASES"
if releases_file.exists():
shutil.copy2(releases_file, release_folder / "RELEASES")
print(f"✅ RELEASES file copied")
else:
print("⚠️ Velopack release file not found")
else:
print(f"⚠️ Velopack packaging failed: {result.stderr}")
print("💡 Make sure 'vpk' tool is installed: dotnet tool install -g vpk")
except Exception as e:
print(f"⚠️ Velopack packaging failed: {e}")
print("💡 Make sure 'vpk' tool is installed: dotnet tool install -g vpk")
# Step 5: Copy the standalone single-file binary to release folder
print(f"\n📦 Step 5: Adding standalone single-file binary...")
standalone_binary = release_folder / "AkademiTrack"
shutil.copy2(binary_single, standalone_binary)
os.chmod(standalone_binary, 0o755)
standalone_size = standalone_binary.stat().st_size / 1024 / 1024
print(f"✅ Standalone single-file binary: {standalone_binary.name} ({standalone_size:.1f} MB)")
# Step 6: Create install script
print(f"\n📦 Step 6: Creating install script...")
install_script = release_folder / "install.sh"
install_content = f"""#!/bin/bash
# AkademiTrack Linux Installation Script
INSTALL_DIR="/opt/akademitrack"
DESKTOP_FILE="/usr/share/applications/akademitrack.desktop"
BIN_LINK="/usr/local/bin/akademitrack"
echo "🚀 Installing AkademiTrack v{version}..."
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "❌ Please run as root (use sudo)"
exit 1
fi
# Create install directory
echo "📁 Creating installation directory..."
mkdir -p "$INSTALL_DIR"
# Extract files
echo "📦 Extracting files..."
tar -xzf AkademiTrack-linux-Portable.tar.gz -C /opt/
mv /opt/AkademiTrack/* "$INSTALL_DIR/"
rmdir /opt/AkademiTrack
# Set permissions
echo "🔐 Setting permissions..."
chmod +x "$INSTALL_DIR/AkademiTrack"
# Create desktop entry
echo "🖥️ Creating desktop entry..."
cat > "$DESKTOP_FILE" << 'EOF'
[Desktop Entry]
Version={version}
Type=Application
Name=AkademiTrack
Comment=Academic tracking and management application
Exec=/opt/akademitrack/AkademiTrack
Icon=/opt/akademitrack/akademitrack
Terminal=false
Categories=Education;Office;
StartupWMClass=AkademiTrack
EOF
# Create symlink
echo "🔗 Creating symlink..."
ln -sf "$INSTALL_DIR/AkademiTrack" "$BIN_LINK"
# Update desktop database
if command -v update-desktop-database &> /dev/null; then
update-desktop-database /usr/share/applications
fi
echo "✅ Installation complete!"
echo "📋 You can now:"
echo " • Run 'akademitrack' from the terminal"
echo " • Launch from your application menu"
echo ""
echo "💡 To uninstall, run: sudo rm -rf $INSTALL_DIR $DESKTOP_FILE $BIN_LINK"
"""
with open(install_script, 'w') as f:
f.write(install_content)
os.chmod(install_script, 0o755)
print(f"✅ Install script created: {install_script.name}")
# Verify the release folder exists and has files
if not release_folder.exists():
print(f"❌ Release folder was not created!")
return False
files_in_release = list(release_folder.iterdir())
if not files_in_release:
print(f"❌ Release folder is empty!")
return False
return release_folder
def main():
print("🚀 AkademiTrack Linux Build & Package Tool")
print("=" * 50)
# Get version number
version = get_version_input()
print(f"\n📌 Using version: {version}")
# Ask if user wants to update .csproj
update_proj = input("\nUpdate version in .csproj file? (y/n) [n]: ").strip().lower()
if update_proj == 'y': # Only updates if you type 'y'
update_csproj_version(version)
# Build everything
release_folder = build_linux_release(version)
if release_folder:
print("\n" + "=" * 50)
print("🎉 Build completed successfully!")
print(f"📦 Version: {version}")
print(f"\n📁 Release folder: {release_folder}/")
print("\n📋 Contents:")
# List all files in release folder
for item in sorted(release_folder.iterdir()):
if item.is_file():
size = item.stat().st_size / 1024 / 1024
print(f" ✅ {item.name} ({size:.1f} MB)")
print("\n📋 Files created:")
print(f" • AkademiTrack - Standalone single-file binary (RUN THIS ONE!)")
print(f" • AkademiTrack-linux-Portable.tar.gz - Portable tarball package")
print(f" • AkademiTrack-{version}-*.nupkg - Velopack release package (for auto-updates)")
print(f" • RELEASES - Velopack releases index file")
print(f" • install.sh - Installation script (sudo ./install.sh)")
print("\n💡 Next steps:")
print(f" • Test standalone binary: ./{release_folder}/AkademiTrack")
print(f" • Or install system-wide: cd {release_folder} && sudo ./install.sh")
print(f" • Distribute the tarball for manual installs")
print(f" • Use the .nupkg + RELEASES for Velopack auto-updates")
print("\n🔧 Installation options:")
print(" 1. Standalone: Just run ./AkademiTrack")
print(" 2. System-wide: sudo ./install.sh (installs to /opt/akademitrack)")
print(" 3. Extract tarball: tar -xzf AkademiTrack-linux-Portable.tar.gz")
else:
print("\n❌ Build failed!")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n⚠️ Build interrupted by user")
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()