Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0faf0fb360 | |||
| 9be53c7e22 | |||
| d80b025219 | |||
| 91fe17cda2 | |||
| 4b12e21dd4 | |||
| e402720a12 |
@@ -70,3 +70,58 @@ jobs:
|
|||||||
path: dist/**
|
path: dist/**
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install MinGW toolchain
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
make \
|
||||||
|
binutils-mingw-w64-i686 \
|
||||||
|
binutils-mingw-w64-x86-64 \
|
||||||
|
g++-mingw-w64-i686 \
|
||||||
|
g++-mingw-w64-x86-64
|
||||||
|
|
||||||
|
- name: Read VERSION
|
||||||
|
id: version_linux
|
||||||
|
run: |
|
||||||
|
v="$(tr -d '\r\n' < VERSION)"
|
||||||
|
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.config }} on Linux
|
||||||
|
run: python build_plugin.py --host linux --configs ${{ matrix.config }} --verbose
|
||||||
|
|
||||||
|
- name: Collect DLL
|
||||||
|
run: |
|
||||||
|
case "${{ matrix.config }}" in
|
||||||
|
x86-ansi) dir="x86-ansi" ;;
|
||||||
|
x86-unicode) dir="x86-unicode" ;;
|
||||||
|
x64-unicode) dir="amd64-unicode" ;;
|
||||||
|
esac
|
||||||
|
dst="dist/${{ matrix.config }}"
|
||||||
|
mkdir -p "$dst"
|
||||||
|
cp "plugins/$dir/nsis7z.dll" "$dst/"
|
||||||
|
|
||||||
|
- name: Upload DLL
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }}-${{ steps.version_linux.outputs.version }}-${{ matrix.config }}-linux
|
||||||
|
path: dist/**
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
|
|||||||
@@ -68,8 +68,53 @@ jobs:
|
|||||||
name: pkg-${{ matrix.config }}
|
name: pkg-${{ matrix.config }}
|
||||||
path: '*.zip'
|
path: '*.zip'
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install MinGW toolchain
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
make \
|
||||||
|
binutils-mingw-w64-i686 \
|
||||||
|
binutils-mingw-w64-x86-64 \
|
||||||
|
g++-mingw-w64-i686 \
|
||||||
|
g++-mingw-w64-x86-64
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.config }} on Linux
|
||||||
|
run: python build_plugin.py --host linux --configs ${{ matrix.config }}
|
||||||
|
|
||||||
|
- name: Collect DLL
|
||||||
|
run: |
|
||||||
|
case "${{ matrix.config }}" in
|
||||||
|
x86-ansi) dir="x86-ansi" ;;
|
||||||
|
x86-unicode) dir="x86-unicode" ;;
|
||||||
|
x64-unicode) dir="amd64-unicode" ;;
|
||||||
|
esac
|
||||||
|
dst="dist/${{ matrix.config }}"
|
||||||
|
mkdir -p "$dst"
|
||||||
|
cp "plugins/$dir/nsis7z.dll" "$dst/"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux-build-${{ matrix.config }}
|
||||||
|
path: dist/**
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: [build, build-linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "versions/7-zip-zstd"]
|
||||||
|
path = versions/7-zip-zstd
|
||||||
|
url = https://github.com/mcmilk/7-Zip-zstd.git
|
||||||
+15
-1
@@ -7,6 +7,19 @@ e il progetto aderisce al [Semantic Versioning](https://semver.org/spec/v2.0.0.h
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.1.0] — 2026-04-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- NSIS plugin API (`pluginapi.cpp`/`pluginapi.h`) for 7-Zip zstd build
|
||||||
|
- `ExtractCallbackConsole` class for extraction progress and user input
|
||||||
|
- Main extraction logic (`Main.cpp`, `MainAr.cpp`) for the zstd bundle
|
||||||
|
- User input utilities (`UserInputUtils2`) for password handling and prompts
|
||||||
|
- Break signal handling (`NSISBreak`) for graceful interruption
|
||||||
|
- `Nsis7z_vs2026.vcxproj` (v145 toolset) for Visual Studio 2026 builds
|
||||||
|
- `tools/fix_vcxproj.py` helper for project file patching
|
||||||
|
- `versions/7-zip-zstd` submodule added
|
||||||
|
- `build_zstd.cmd` top-level build script for the zstd variant
|
||||||
|
|
||||||
## [2.0.1] — 2026-04-29
|
## [2.0.1] — 2026-04-29
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -23,6 +36,7 @@ e il progetto aderisce al [Semantic Versioning](https://semver.org/spec/v2.0.0.h
|
|||||||
- CI/CD via GitHub Actions (mirror automatico Gitea → GitHub)
|
- CI/CD via GitHub Actions (mirror automatico Gitea → GitHub)
|
||||||
- Documentazione completa (README, CONTRIBUTING, SECURITY)
|
- Documentazione completa (README, CONTRIBUTING, SECURITY)
|
||||||
|
|
||||||
[Unreleased]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.0.1...HEAD
|
[Unreleased]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.1.0...HEAD
|
||||||
|
[2.1.0]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.0.1...v2.1.0
|
||||||
[2.0.1]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.0.0...v2.0.1
|
[2.0.1]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.0.0...v2.0.1
|
||||||
[2.0.0]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/releases/tag/v2.0.0
|
[2.0.0]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/releases/tag/v2.0.0
|
||||||
|
|||||||
@@ -61,10 +61,17 @@ python build_plugin.py --7zip-version 25.01
|
|||||||
# Specific toolset (2022|2026|auto)
|
# Specific toolset (2022|2026|auto)
|
||||||
python build_plugin.py --toolset 2022
|
python build_plugin.py --toolset 2022
|
||||||
|
|
||||||
|
# Linux host path (cross-build with MinGW-w64, 26.00)
|
||||||
|
python build_plugin.py --host linux
|
||||||
|
|
||||||
# Print version and exit
|
# Print version and exit
|
||||||
python build_plugin.py --version
|
python build_plugin.py --version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Linux notes:
|
||||||
|
- Local Linux path currently supports `--7zip-version 26.00`.
|
||||||
|
- Requires MinGW-w64 toolchains (`x86_64-w64-mingw32-*` and `i686-w64-mingw32-*`) and `make`.
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@echo off
|
||||||
|
:: Generic wrapper for build_plugin.py
|
||||||
|
:: Usage: build.cmd [--7zip-version 19.00|25.01|26.00|zstd] [--toolset 2022|2026|auto] [...]
|
||||||
|
:: Default: --7zip-version 26.00 --toolset auto
|
||||||
|
setlocal
|
||||||
|
python "%~dp0build_plugin.py" %*
|
||||||
|
exit /b %errorlevel%
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
:: Build ns7zip using 7-Zip 19.00 sources
|
||||||
|
:: Usage: build_1900.cmd [--toolset 2022|2026|auto] [...]
|
||||||
|
setlocal
|
||||||
|
python "%~dp0build_plugin.py" --7zip-version 19.00 %*
|
||||||
|
exit /b %errorlevel%
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
:: Build ns7zip using 7-Zip 25.01 sources
|
||||||
|
:: Usage: build_2501.cmd [--toolset 2022|2026|auto] [...]
|
||||||
|
setlocal
|
||||||
|
python "%~dp0build_plugin.py" --7zip-version 25.01 %*
|
||||||
|
exit /b %errorlevel%
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
:: Build ns7zip using 7-Zip 26.00 sources (default)
|
||||||
|
:: Usage: build_2600.cmd [--toolset 2022|2026|auto] [...]
|
||||||
|
setlocal
|
||||||
|
python "%~dp0build_plugin.py" --7zip-version 26.00 %*
|
||||||
|
exit /b %errorlevel%
|
||||||
+23
-6
@@ -5,7 +5,7 @@ Wraps per-version/per-toolset build scripts.
|
|||||||
Defaults: 7-Zip 26.00, VS2026 (v145 toolset)
|
Defaults: 7-Zip 26.00, VS2026 (v145 toolset)
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import argparse, subprocess, sys
|
import argparse, platform, subprocess, sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -16,23 +16,30 @@ class Colors:
|
|||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent
|
ROOT = Path(__file__).resolve().parent
|
||||||
SCRIPTS = {
|
WINDOWS_SCRIPTS = {
|
||||||
"19.00": {"2022": "tools/legacy/build_plugin_vs2022.py",
|
"19.00": {"2022": "tools/legacy/build_plugin_vs2022.py",
|
||||||
"2026": "tools/legacy/build_plugin_vs2026.py"},
|
"2026": "tools/legacy/build_plugin_vs2026.py"},
|
||||||
"25.01": {"2022": "tools/legacy/build_plugin_2501_vs2022.py",
|
"25.01": {"2022": "tools/legacy/build_plugin_2501_vs2022.py",
|
||||||
"2026": "tools/legacy/build_plugin_2501_vs2026.py"},
|
"2026": "tools/legacy/build_plugin_2501_vs2026.py"},
|
||||||
"26.00": {"2022": None,
|
"26.00": {"2022": None,
|
||||||
"2026": "tools/legacy/build_plugin_2600_vs2026.py"},
|
"2026": "tools/legacy/build_plugin_2600_vs2026.py"},
|
||||||
|
"zstd": {"2022": None,
|
||||||
|
"2026": "tools/legacy/build_plugin_zstd_vs2026.py"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LINUX_SCRIPT = "tools/linux/build_plugin_linux.py"
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Build nsis7z NSIS plugin")
|
parser = argparse.ArgumentParser(description="Build nsis7z NSIS plugin")
|
||||||
parser.add_argument("--7zip-version", dest="zip_version",
|
parser.add_argument("--7zip-version", dest="zip_version",
|
||||||
choices=["19.00", "25.01", "26.00"], default="26.00",
|
choices=["19.00", "25.01", "26.00", "zstd"], default="26.00",
|
||||||
help="7-Zip version to build (default: 26.00)")
|
help="7-Zip version to build (default: 26.00); "
|
||||||
|
"'zstd' uses mcmilk/7-Zip-zstd submodule")
|
||||||
parser.add_argument("--toolset", choices=["2022", "2026", "auto"], default="auto",
|
parser.add_argument("--toolset", choices=["2022", "2026", "auto"], default="auto",
|
||||||
help="Visual Studio toolset version (default: auto)")
|
help="Visual Studio toolset version (default: auto)")
|
||||||
|
parser.add_argument("--host", choices=["auto", "windows", "linux"], default="auto",
|
||||||
|
help="Build host path (default: auto detect)")
|
||||||
parser.add_argument("--version", action="store_true",
|
parser.add_argument("--version", action="store_true",
|
||||||
help="Print plugin version and exit")
|
help="Print plugin version and exit")
|
||||||
known, rest = parser.parse_known_args()
|
known, rest = parser.parse_known_args()
|
||||||
@@ -42,14 +49,24 @@ def main() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
ver = (ROOT / "VERSION").read_text(encoding="utf-8-sig").strip()
|
ver = (ROOT / "VERSION").read_text(encoding="utf-8-sig").strip()
|
||||||
|
zip_label = "7z-ZS" if known.zip_version == "zstd" else f"7z {known.zip_version}"
|
||||||
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}=== Building ns7zip v{ver} "
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}=== Building ns7zip v{ver} "
|
||||||
f"(7z {known.zip_version}) ==={Colors.RESET}")
|
f"({zip_label}) ==={Colors.RESET}")
|
||||||
|
|
||||||
|
host = known.host
|
||||||
|
if host == "auto":
|
||||||
|
host = "windows" if platform.system().lower().startswith("win") else "linux"
|
||||||
|
|
||||||
|
if host == "linux":
|
||||||
|
script = ROOT / LINUX_SCRIPT
|
||||||
|
cmd = [sys.executable, str(script), "--7zip-version", known.zip_version] + rest
|
||||||
|
return subprocess.run(cmd).returncode
|
||||||
|
|
||||||
toolset = known.toolset
|
toolset = known.toolset
|
||||||
if toolset == "auto":
|
if toolset == "auto":
|
||||||
toolset = "2026"
|
toolset = "2026"
|
||||||
|
|
||||||
script_rel = SCRIPTS[known.zip_version][toolset]
|
script_rel = WINDOWS_SCRIPTS[known.zip_version][toolset]
|
||||||
if script_rel is None:
|
if script_rel is None:
|
||||||
print(f"ERROR: no {known.zip_version} script for toolset {toolset}", file=sys.stderr)
|
print(f"ERROR: no {known.zip_version} script for toolset {toolset}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
:: Build ns7zip using 7-Zip-zstd sources (mcmilk fork)
|
||||||
|
:: Usage: build_zstd.cmd [--toolset 2022|2026|auto] [...]
|
||||||
|
setlocal
|
||||||
|
python "%~dp0build_plugin.py" --7zip-version zstd %*
|
||||||
|
exit /b %errorlevel%
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -28,6 +28,69 @@ import threading
|
|||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BuildConfig:
|
class BuildConfig:
|
||||||
"""Build configuration settings"""
|
"""Build configuration settings"""
|
||||||
@@ -208,44 +271,42 @@ def get_memory_optimizations() -> List[str]:
|
|||||||
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
"""Print CPU information for build optimization"""
|
"""Print CPU information for build optimization"""
|
||||||
if not use_parallel:
|
if not use_parallel:
|
||||||
print("Build mode: Single-threaded")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
return
|
return
|
||||||
|
|
||||||
cpu_info = get_cpu_info()
|
cpu_info = get_cpu_info()
|
||||||
optimal_threads = get_optimal_thread_count()
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
print("Build mode: Parallel")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
print(f"Logical cores: {cpu_info['logical_cores']}")
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
print(f"Physical cores: {cpu_info['physical_cores']}")
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
if cpu_info['has_hyperthreading']:
|
if cpu_info['has_hyperthreading']:
|
||||||
print("Hyperthreading: ENABLED")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads} (using physical cores)")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
else:
|
else:
|
||||||
print("Hyperthreading: NOT AVAILABLE")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads}")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"MSBuild threads: {optimal_threads}")
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (parallel, memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
print(f"Available memory: {memory_gb:.1f} GB")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Available memory: Unknown (install psutil for details)")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print("Optimizations: DISABLED")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
if not cpu_info['has_psutil']:
|
if not cpu_info['has_psutil']:
|
||||||
print("Note: Install 'psutil' for detailed CPU/memory info (pip install psutil)")
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
def build_configuration(
|
def build_configuration(
|
||||||
msbuild_path: Path,
|
msbuild_path: Path,
|
||||||
project_file: Path,
|
project_file: Path,
|
||||||
@@ -296,12 +357,12 @@ def build_configuration(
|
|||||||
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
if not capture_output:
|
if not capture_output:
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
if counter:
|
if counter:
|
||||||
print(f"Building {config.name} [{counter}]")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print(f"Building {config.name}...")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
print('='*50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
# Execute build and measure time
|
# Execute build and measure time
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -339,7 +400,7 @@ def copy_output(
|
|||||||
dest_dir = plugins_dir / config.dest_dir
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
if not output_file.exists():
|
if not output_file.exists():
|
||||||
print(f"ERROR: {config.name} DLL not found at {output_file}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
# Create destination directory
|
# Create destination directory
|
||||||
@@ -350,16 +411,16 @@ def copy_output(
|
|||||||
dest_path = dest_dir / 'nsis7z.dll'
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
shutil.copy2(output_file, dest_path)
|
shutil.copy2(output_file, dest_path)
|
||||||
file_size = output_file.stat().st_size
|
file_size = output_file.stat().st_size
|
||||||
print(f"SUCCESS: {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
return True, file_size, dest_path
|
return True, file_size, dest_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to copy {config.name}: {e}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
"""Clean up build artifacts"""
|
"""Clean up build artifacts"""
|
||||||
print("\nCleaning build artifacts...")
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
|
||||||
@@ -373,11 +434,11 @@ def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None
|
|||||||
if dir_path.exists():
|
if dir_path.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
print(f" - Cleaned: {dir_path.name}")
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" - Failed to clean {dir_path.name}: {e}")
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
print("Build artifacts cleaned successfully.")
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def format_time(seconds: float) -> str:
|
def format_time(seconds: float) -> str:
|
||||||
@@ -512,8 +573,8 @@ def _build_configs_parallel(
|
|||||||
in the same order as *configs*.
|
in the same order as *configs*.
|
||||||
"""
|
"""
|
||||||
n = len(configs)
|
n = len(configs)
|
||||||
print(f"\nParallel-configs: launching {n} builds simultaneously...")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
total_start = time.time()
|
total_start = time.time()
|
||||||
results_by_idx: dict = {}
|
results_by_idx: dict = {}
|
||||||
@@ -537,35 +598,39 @@ def _build_configs_parallel(
|
|||||||
|
|
||||||
with _print_lock:
|
with _print_lock:
|
||||||
all_ok = success and copy_ok
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
tag = "OK" if all_ok else "FAILED"
|
tag = "OK" if all_ok else "FAILED"
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f"[{tag}] {config.name} ({format_time(build_time)}) {size_str}")
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
if dest_path:
|
if dest_path:
|
||||||
print(f" -> {dest_path}")
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
if not all_ok:
|
if not all_ok:
|
||||||
copy_out = copy_buf.getvalue()
|
copy_out = copy_buf.getvalue()
|
||||||
if copy_out.strip():
|
if copy_out.strip():
|
||||||
print(copy_out.rstrip())
|
print(copy_out.rstrip())
|
||||||
if captured.strip():
|
if captured.strip():
|
||||||
print("--- Build output ---")
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
print(captured.rstrip())
|
print(captured.rstrip())
|
||||||
|
|
||||||
with idx_lock:
|
with idx_lock:
|
||||||
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=n) as executor:
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
for fut in as_completed(futures):
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
exc = fut.exception()
|
for fut in as_completed(futures):
|
||||||
if exc:
|
exc = fut.exception()
|
||||||
idx = futures[fut]
|
if exc:
|
||||||
with _print_lock:
|
idx = futures[fut]
|
||||||
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
with _print_lock:
|
||||||
with idx_lock:
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
wall_time = time.time() - total_start
|
wall_time = time.time() - total_start
|
||||||
print(f"\nAll {n} configs finished in {format_time(wall_time)} (wall clock)")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
return [results_by_idx[i] for i in range(n)]
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -724,15 +789,15 @@ Examples:
|
|||||||
configs_to_build = [CONFIGS[name] for name in args.configs]
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Building nsis7z plugin (7-Zip 25.01) - {len(configs_to_build)} configuration(s)")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin (7-Zip 25.01){Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Visual Studio: {vs_version_name} (toolset {platform_toolset})")
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
print(f"MSBuild: {msbuild_path}")
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
print(f"Project: {project_file}")
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
print(f"Plugins: {plugins_dir}")
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
print(f"Rebuild: {args.rebuild}")
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
print(f"Verbosity: {args.verbosity}")
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Print CPU and parallel build info
|
# Print CPU and parallel build info
|
||||||
@@ -826,12 +891,13 @@ Examples:
|
|||||||
total_time = time.time() - total_start_time
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
print("\n" + "=" * 50)
|
print()
|
||||||
all_success = all(success for _, success, _, _, _ in build_results)
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
print("ALL BUILDS SUCCESSFUL!")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("\nPlugins copied to:")
|
print("\nPlugins copied to:")
|
||||||
for config, _, build_time, file_size, dest_path in build_results:
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
@@ -842,22 +908,24 @@ Examples:
|
|||||||
print()
|
print()
|
||||||
clean_build_artifacts(project_dir, configs_to_build)
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
else:
|
else:
|
||||||
print("SOME BUILDS FAILED!")
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("\nFailed configurations:")
|
print("\nFailed configurations:")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
if not success:
|
if not success:
|
||||||
print(f" - {config.name}")
|
print(f" - {config.name}")
|
||||||
|
|
||||||
# Show timing summary
|
# Show timing summary
|
||||||
print("\n" + "-" * 50)
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print("Build Summary:")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
status = "OK" if success else "FAIL"
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f" {status} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
print("-" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print(f"Total time: {format_time(total_time)}")
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Pause if requested
|
# Pause if requested
|
||||||
|
|||||||
@@ -21,6 +21,69 @@ from typing import List, Tuple, Optional
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
class BuildConfig:
|
class BuildConfig:
|
||||||
"""Configuration for a single build target"""
|
"""Configuration for a single build target"""
|
||||||
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
@@ -84,6 +147,7 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
str(_VSWHERE_PATH),
|
str(_VSWHERE_PATH),
|
||||||
|
'-products', '*',
|
||||||
'-version', _VS_VERSION_RANGE[ver],
|
'-version', _VS_VERSION_RANGE[ver],
|
||||||
'-latest',
|
'-latest',
|
||||||
'-requires', 'Microsoft.Component.MSBuild',
|
'-requires', 'Microsoft.Component.MSBuild',
|
||||||
@@ -102,6 +166,13 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Numeric version folder used by VS2026 installer (version 18.x)
|
||||||
|
_VS_NUMERIC_FOLDER = {
|
||||||
|
'2026': '18',
|
||||||
|
'2022': '2022',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
||||||
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
||||||
|
|
||||||
@@ -114,11 +185,16 @@ def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
|||||||
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
||||||
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
for version in versions_to_try:
|
for version in versions_to_try:
|
||||||
base_path = Path(rf'C:\Program Files\Microsoft Visual Studio\{version}')
|
numeric = _VS_NUMERIC_FOLDER.get(version, version)
|
||||||
for edition in vs_editions:
|
base_paths = [
|
||||||
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
Path(rf'C:\Program Files (x86)\Microsoft Visual Studio\{numeric}'),
|
||||||
if msbuild_path.exists():
|
Path(rf'C:\Program Files\Microsoft Visual Studio\{version}'),
|
||||||
return msbuild_path, _VS_TOOLSET[version], version
|
]
|
||||||
|
for base_path in base_paths:
|
||||||
|
for edition in vs_editions:
|
||||||
|
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
||||||
|
if msbuild_path.exists():
|
||||||
|
return msbuild_path, _VS_TOOLSET[version], version
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_project_paths() -> Tuple[Path, Path, Path]:
|
def get_project_paths() -> Tuple[Path, Path, Path]:
|
||||||
@@ -209,41 +285,41 @@ def get_memory_optimizations() -> List[str]:
|
|||||||
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
"""Print CPU information for build optimization"""
|
"""Print CPU information for build optimization"""
|
||||||
if not use_parallel:
|
if not use_parallel:
|
||||||
print("Build mode: Single-threaded")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
return
|
return
|
||||||
|
|
||||||
cpu_info = get_cpu_info()
|
cpu_info = get_cpu_info()
|
||||||
optimal_threads = get_optimal_thread_count()
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
print("Build mode: Parallel")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
print(f"Logical cores: {cpu_info['logical_cores']}")
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
print(f"Physical cores: {cpu_info['physical_cores']}")
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
if cpu_info['has_hyperthreading']:
|
if cpu_info['has_hyperthreading']:
|
||||||
print("Hyperthreading: ENABLED")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads} (using physical cores)")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
else:
|
else:
|
||||||
print("Hyperthreading: NOT AVAILABLE")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads}")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"MSBuild threads: {optimal_threads}")
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (parallel, memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
print(f"Available memory: {memory_gb:.1f} GB")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Available memory: Unknown (install psutil for details)")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print("Optimizations: DISABLED")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
if not cpu_info['has_psutil']:
|
if not cpu_info['has_psutil']:
|
||||||
print("Note: Install 'psutil' for detailed CPU/memory info (pip install psutil)")
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
@@ -297,12 +373,12 @@ def build_configuration(
|
|||||||
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
if not capture_output:
|
if not capture_output:
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
if counter:
|
if counter:
|
||||||
print(f"Building {config.name} [{counter}]")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print(f"Building {config.name}...")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
print('='*50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
# Execute build and measure time
|
# Execute build and measure time
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -340,7 +416,7 @@ def copy_output(
|
|||||||
dest_dir = plugins_dir / config.dest_dir
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
if not output_file.exists():
|
if not output_file.exists():
|
||||||
print(f"ERROR: {config.name} DLL not found at {output_file}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
# Create destination directory
|
# Create destination directory
|
||||||
@@ -351,16 +427,16 @@ def copy_output(
|
|||||||
dest_path = dest_dir / 'nsis7z.dll'
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
shutil.copy2(output_file, dest_path)
|
shutil.copy2(output_file, dest_path)
|
||||||
file_size = output_file.stat().st_size
|
file_size = output_file.stat().st_size
|
||||||
print(f"SUCCESS: {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
return True, file_size, dest_path
|
return True, file_size, dest_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to copy {config.name}: {e}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
"""Clean up build artifacts"""
|
"""Clean up build artifacts"""
|
||||||
print("\nCleaning build artifacts...")
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
|
||||||
@@ -374,11 +450,11 @@ def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None
|
|||||||
if dir_path.exists():
|
if dir_path.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
print(f" - Cleaned: {dir_path.name}")
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" - Failed to clean {dir_path.name}: {e}")
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
print("Build artifacts cleaned successfully.")
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def format_time(seconds: float) -> str:
|
def format_time(seconds: float) -> str:
|
||||||
@@ -513,8 +589,8 @@ def _build_configs_parallel(
|
|||||||
in the same order as *configs*.
|
in the same order as *configs*.
|
||||||
"""
|
"""
|
||||||
n = len(configs)
|
n = len(configs)
|
||||||
print(f"\nParallel-configs: launching {n} builds simultaneously...")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
total_start = time.time()
|
total_start = time.time()
|
||||||
results_by_idx: dict = {}
|
results_by_idx: dict = {}
|
||||||
@@ -538,35 +614,39 @@ def _build_configs_parallel(
|
|||||||
|
|
||||||
with _print_lock:
|
with _print_lock:
|
||||||
all_ok = success and copy_ok
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
tag = "OK" if all_ok else "FAILED"
|
tag = "OK" if all_ok else "FAILED"
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f"[{tag}] {config.name} ({format_time(build_time)}) {size_str}")
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
if dest_path:
|
if dest_path:
|
||||||
print(f" -> {dest_path}")
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
if not all_ok:
|
if not all_ok:
|
||||||
copy_out = copy_buf.getvalue()
|
copy_out = copy_buf.getvalue()
|
||||||
if copy_out.strip():
|
if copy_out.strip():
|
||||||
print(copy_out.rstrip())
|
print(copy_out.rstrip())
|
||||||
if captured.strip():
|
if captured.strip():
|
||||||
print("--- Build output ---")
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
print(captured.rstrip())
|
print(captured.rstrip())
|
||||||
|
|
||||||
with idx_lock:
|
with idx_lock:
|
||||||
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=n) as executor:
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
for fut in as_completed(futures):
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
exc = fut.exception()
|
for fut in as_completed(futures):
|
||||||
if exc:
|
exc = fut.exception()
|
||||||
idx = futures[fut]
|
if exc:
|
||||||
with _print_lock:
|
idx = futures[fut]
|
||||||
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
with _print_lock:
|
||||||
with idx_lock:
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
wall_time = time.time() - total_start
|
wall_time = time.time() - total_start
|
||||||
print(f"\nAll {n} configs finished in {format_time(wall_time)} (wall clock)")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
return [results_by_idx[i] for i in range(n)]
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -736,15 +816,15 @@ Examples:
|
|||||||
configs_to_build = [CONFIGS[name] for name in args.configs]
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Building nsis7z plugin (7-Zip 25.01, VS2026) - {len(configs_to_build)} configuration(s)")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin (7-Zip 25.01, VS2026){Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Visual Studio: {vs_version_name} (toolset {platform_toolset})")
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
print(f"MSBuild: {msbuild_path}")
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
print(f"Project: {project_file}")
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
print(f"Plugins: {plugins_dir}")
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
print(f"Rebuild: {args.rebuild}")
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
print(f"Verbosity: {args.verbosity}")
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Print CPU and parallel build info
|
# Print CPU and parallel build info
|
||||||
@@ -838,12 +918,13 @@ Examples:
|
|||||||
total_time = time.time() - total_start_time
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
print("\n" + "=" * 50)
|
print()
|
||||||
all_success = all(success for _, success, _, _, _ in build_results)
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
print("ALL BUILDS SUCCESSFUL!")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("\nPlugins copied to:")
|
print("\nPlugins copied to:")
|
||||||
for config, _, build_time, file_size, dest_path in build_results:
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
@@ -854,22 +935,24 @@ Examples:
|
|||||||
print()
|
print()
|
||||||
clean_build_artifacts(project_dir, configs_to_build)
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
else:
|
else:
|
||||||
print("SOME BUILDS FAILED!")
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("\nFailed configurations:")
|
print("\nFailed configurations:")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
if not success:
|
if not success:
|
||||||
print(f" - {config.name}")
|
print(f" - {config.name}")
|
||||||
|
|
||||||
# Show timing summary
|
# Show timing summary
|
||||||
print("\n" + "-" * 50)
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print("Build Summary:")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
status = "OK" if success else "FAIL"
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f" {status} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
print("-" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print(f"Total time: {format_time(total_time)}")
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Pause if requested
|
# Pause if requested
|
||||||
|
|||||||
@@ -21,6 +21,69 @@ from typing import List, Tuple, Optional
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
class BuildConfig:
|
class BuildConfig:
|
||||||
"""Configuration for a single build target"""
|
"""Configuration for a single build target"""
|
||||||
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
@@ -84,6 +147,7 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
str(_VSWHERE_PATH),
|
str(_VSWHERE_PATH),
|
||||||
|
'-products', '*',
|
||||||
'-version', _VS_VERSION_RANGE[ver],
|
'-version', _VS_VERSION_RANGE[ver],
|
||||||
'-latest',
|
'-latest',
|
||||||
'-requires', 'Microsoft.Component.MSBuild',
|
'-requires', 'Microsoft.Component.MSBuild',
|
||||||
@@ -102,6 +166,13 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Numeric version folder used by VS2026 installer (version 18.x)
|
||||||
|
_VS_NUMERIC_FOLDER = {
|
||||||
|
'2026': '18',
|
||||||
|
'2022': '2022',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
||||||
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
||||||
|
|
||||||
@@ -114,11 +185,16 @@ def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
|||||||
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
||||||
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
for version in versions_to_try:
|
for version in versions_to_try:
|
||||||
base_path = Path(rf'C:\Program Files\Microsoft Visual Studio\{version}')
|
numeric = _VS_NUMERIC_FOLDER.get(version, version)
|
||||||
for edition in vs_editions:
|
base_paths = [
|
||||||
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
Path(rf'C:\Program Files (x86)\Microsoft Visual Studio\{numeric}'),
|
||||||
if msbuild_path.exists():
|
Path(rf'C:\Program Files\Microsoft Visual Studio\{version}'),
|
||||||
return msbuild_path, _VS_TOOLSET[version], version
|
]
|
||||||
|
for base_path in base_paths:
|
||||||
|
for edition in vs_editions:
|
||||||
|
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
||||||
|
if msbuild_path.exists():
|
||||||
|
return msbuild_path, _VS_TOOLSET[version], version
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_project_paths(toolset: str = 'v145') -> Tuple[Path, Path, Path]:
|
def get_project_paths(toolset: str = 'v145') -> Tuple[Path, Path, Path]:
|
||||||
@@ -210,41 +286,41 @@ def get_memory_optimizations() -> List[str]:
|
|||||||
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
"""Print CPU information for build optimization"""
|
"""Print CPU information for build optimization"""
|
||||||
if not use_parallel:
|
if not use_parallel:
|
||||||
print("Build mode: Single-threaded")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
return
|
return
|
||||||
|
|
||||||
cpu_info = get_cpu_info()
|
cpu_info = get_cpu_info()
|
||||||
optimal_threads = get_optimal_thread_count()
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
print("Build mode: Parallel")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
print(f"Logical cores: {cpu_info['logical_cores']}")
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
print(f"Physical cores: {cpu_info['physical_cores']}")
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
if cpu_info['has_hyperthreading']:
|
if cpu_info['has_hyperthreading']:
|
||||||
print("Hyperthreading: ENABLED")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads} (using physical cores)")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
else:
|
else:
|
||||||
print("Hyperthreading: NOT AVAILABLE")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads}")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"MSBuild threads: {optimal_threads}")
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (parallel, memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
print(f"Available memory: {memory_gb:.1f} GB")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Available memory: Unknown (install psutil for details)")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print("Optimizations: DISABLED")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
if not cpu_info['has_psutil']:
|
if not cpu_info['has_psutil']:
|
||||||
print("Note: Install 'psutil' for detailed CPU/memory info (pip install psutil)")
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
@@ -299,12 +375,12 @@ def build_configuration(
|
|||||||
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
if not capture_output:
|
if not capture_output:
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
if counter:
|
if counter:
|
||||||
print(f"Building {config.name} [{counter}]")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print(f"Building {config.name}...")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
print('='*50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
# Execute build and measure time
|
# Execute build and measure time
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -342,7 +418,7 @@ def copy_output(
|
|||||||
dest_dir = plugins_dir / config.dest_dir
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
if not output_file.exists():
|
if not output_file.exists():
|
||||||
print(f"ERROR: {config.name} DLL not found at {output_file}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
# Create destination directory
|
# Create destination directory
|
||||||
@@ -353,16 +429,16 @@ def copy_output(
|
|||||||
dest_path = dest_dir / 'nsis7z.dll'
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
shutil.copy2(output_file, dest_path)
|
shutil.copy2(output_file, dest_path)
|
||||||
file_size = output_file.stat().st_size
|
file_size = output_file.stat().st_size
|
||||||
print(f"SUCCESS: {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
return True, file_size, dest_path
|
return True, file_size, dest_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to copy {config.name}: {e}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
"""Clean up build artifacts"""
|
"""Clean up build artifacts"""
|
||||||
print("\nCleaning build artifacts...")
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
|
||||||
@@ -376,11 +452,11 @@ def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None
|
|||||||
if dir_path.exists():
|
if dir_path.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
print(f" - Cleaned: {dir_path.name}")
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" - Failed to clean {dir_path.name}: {e}")
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
print("Build artifacts cleaned successfully.")
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def format_time(seconds: float) -> str:
|
def format_time(seconds: float) -> str:
|
||||||
@@ -516,8 +592,8 @@ def _build_configs_parallel(
|
|||||||
in the same order as *configs*.
|
in the same order as *configs*.
|
||||||
"""
|
"""
|
||||||
n = len(configs)
|
n = len(configs)
|
||||||
print(f"\nParallel-configs: launching {n} builds simultaneously...")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
total_start = time.time()
|
total_start = time.time()
|
||||||
results_by_idx: dict = {}
|
results_by_idx: dict = {}
|
||||||
@@ -541,35 +617,39 @@ def _build_configs_parallel(
|
|||||||
|
|
||||||
with _print_lock:
|
with _print_lock:
|
||||||
all_ok = success and copy_ok
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
tag = "OK" if all_ok else "FAILED"
|
tag = "OK" if all_ok else "FAILED"
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f"[{tag}] {config.name} ({format_time(build_time)}) {size_str}")
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
if dest_path:
|
if dest_path:
|
||||||
print(f" -> {dest_path}")
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
if not all_ok:
|
if not all_ok:
|
||||||
copy_out = copy_buf.getvalue()
|
copy_out = copy_buf.getvalue()
|
||||||
if copy_out.strip():
|
if copy_out.strip():
|
||||||
print(copy_out.rstrip())
|
print(copy_out.rstrip())
|
||||||
if captured.strip():
|
if captured.strip():
|
||||||
print("--- Build output ---")
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
print(captured.rstrip())
|
print(captured.rstrip())
|
||||||
|
|
||||||
with idx_lock:
|
with idx_lock:
|
||||||
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=n) as executor:
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
for fut in as_completed(futures):
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
exc = fut.exception()
|
for fut in as_completed(futures):
|
||||||
if exc:
|
exc = fut.exception()
|
||||||
idx = futures[fut]
|
if exc:
|
||||||
with _print_lock:
|
idx = futures[fut]
|
||||||
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
with _print_lock:
|
||||||
with idx_lock:
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
wall_time = time.time() - total_start
|
wall_time = time.time() - total_start
|
||||||
print(f"\nAll {n} configs finished in {format_time(wall_time)} (wall clock)")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
return [results_by_idx[i] for i in range(n)]
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -731,15 +811,15 @@ Examples:
|
|||||||
configs_to_build = [CONFIGS[name] for name in args.configs]
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*60}{Colors.RESET}")
|
||||||
print(f"Building nsis7z plugin (7-Zip 26.00, VS2026) - {len(configs_to_build)} configuration(s)")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin (7-Zip 26.00, VS2026){Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*60}{Colors.RESET}")
|
||||||
print(f"Visual Studio: {vs_version_name} (toolset {platform_toolset})")
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
print(f"MSBuild: {msbuild_path}")
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
print(f"Project: {project_file}")
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
print(f"Plugins: {plugins_dir}")
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
print(f"Rebuild: {args.rebuild}")
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
print(f"Verbosity: {args.verbosity}")
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Print CPU and parallel build info
|
# Print CPU and parallel build info
|
||||||
@@ -837,12 +917,13 @@ Examples:
|
|||||||
total_time = time.time() - total_start_time
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
print("\n" + "=" * 50)
|
print()
|
||||||
all_success = all(success for _, success, _, _, _ in build_results)
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
print("ALL BUILDS SUCCESSFUL!")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{'='*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{'='*50}{Colors.RESET}")
|
||||||
print("\nPlugins copied to:")
|
print("\nPlugins copied to:")
|
||||||
for config, _, build_time, file_size, dest_path in build_results:
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
@@ -853,22 +934,24 @@ Examples:
|
|||||||
print()
|
print()
|
||||||
clean_build_artifacts(project_dir, configs_to_build)
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
else:
|
else:
|
||||||
print("SOME BUILDS FAILED!")
|
print(f"{Colors.BOLD}{Colors.RED}{'='*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{'='*50}{Colors.RESET}")
|
||||||
print("\nFailed configurations:")
|
print("\nFailed configurations:")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
if not success:
|
if not success:
|
||||||
print(f" - {config.name}")
|
print(f" - {config.name}")
|
||||||
|
|
||||||
# Show timing summary
|
# Show timing summary
|
||||||
print("\n" + "-" * 50)
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}{Colors.RESET}")
|
||||||
print("Build Summary:")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
status = "OK" if success else "FAIL"
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f" {status} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
print("-" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}{Colors.RESET}")
|
||||||
print(f"Total time: {format_time(total_time)}")
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Pause if requested
|
# Pause if requested
|
||||||
|
|||||||
@@ -18,6 +18,69 @@ from typing import List, Tuple, Optional
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
class BuildConfig:
|
class BuildConfig:
|
||||||
"""Configuration for a single build target"""
|
"""Configuration for a single build target"""
|
||||||
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
@@ -206,41 +269,41 @@ def get_memory_optimizations() -> List[str]:
|
|||||||
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
"""Print CPU information for build optimization"""
|
"""Print CPU information for build optimization"""
|
||||||
if not use_parallel:
|
if not use_parallel:
|
||||||
print("Build mode: Single-threaded")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
return
|
return
|
||||||
|
|
||||||
cpu_info = get_cpu_info()
|
cpu_info = get_cpu_info()
|
||||||
optimal_threads = get_optimal_thread_count()
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
print("Build mode: Parallel")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
print(f"Logical cores: {cpu_info['logical_cores']}")
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
print(f"Physical cores: {cpu_info['physical_cores']}")
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
if cpu_info['has_hyperthreading']:
|
if cpu_info['has_hyperthreading']:
|
||||||
print("Hyperthreading: ENABLED")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads} (using physical cores)")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
else:
|
else:
|
||||||
print("Hyperthreading: NOT AVAILABLE")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads}")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"MSBuild threads: {optimal_threads}")
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (parallel, memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
print(f"Available memory: {memory_gb:.1f} GB")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Available memory: Unknown (install psutil for details)")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print("Optimizations: DISABLED")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
if not cpu_info['has_psutil']:
|
if not cpu_info['has_psutil']:
|
||||||
print("Note: Install 'psutil' for detailed CPU/memory info (pip install psutil)")
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
@@ -294,12 +357,12 @@ def build_configuration(
|
|||||||
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
if not capture_output:
|
if not capture_output:
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
if counter:
|
if counter:
|
||||||
print(f"Building {config.name} [{counter}]")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print(f"Building {config.name}...")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
print('='*50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
# Execute build and measure time
|
# Execute build and measure time
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -337,7 +400,7 @@ def copy_output(
|
|||||||
dest_dir = plugins_dir / config.dest_dir
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
if not output_file.exists():
|
if not output_file.exists():
|
||||||
print(f"ERROR: {config.name} DLL not found at {output_file}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
# Create destination directory
|
# Create destination directory
|
||||||
@@ -348,16 +411,16 @@ def copy_output(
|
|||||||
dest_path = dest_dir / 'nsis7z.dll'
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
shutil.copy2(output_file, dest_path)
|
shutil.copy2(output_file, dest_path)
|
||||||
file_size = output_file.stat().st_size
|
file_size = output_file.stat().st_size
|
||||||
print(f"SUCCESS: {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
return True, file_size, dest_path
|
return True, file_size, dest_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to copy {config.name}: {e}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
"""Clean up build artifacts"""
|
"""Clean up build artifacts"""
|
||||||
print("\nCleaning build artifacts...")
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
|
||||||
@@ -371,11 +434,11 @@ def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None
|
|||||||
if dir_path.exists():
|
if dir_path.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
print(f" - Cleaned: {dir_path.name}")
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" - Failed to clean {dir_path.name}: {e}")
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
print("Build artifacts cleaned successfully.")
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def format_time(seconds: float) -> str:
|
def format_time(seconds: float) -> str:
|
||||||
@@ -510,8 +573,8 @@ def _build_configs_parallel(
|
|||||||
in the same order as *configs*.
|
in the same order as *configs*.
|
||||||
"""
|
"""
|
||||||
n = len(configs)
|
n = len(configs)
|
||||||
print(f"\nParallel-configs: launching {n} builds simultaneously...")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
total_start = time.time()
|
total_start = time.time()
|
||||||
results_by_idx: dict = {}
|
results_by_idx: dict = {}
|
||||||
@@ -535,35 +598,39 @@ def _build_configs_parallel(
|
|||||||
|
|
||||||
with _print_lock:
|
with _print_lock:
|
||||||
all_ok = success and copy_ok
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
tag = "OK" if all_ok else "FAILED"
|
tag = "OK" if all_ok else "FAILED"
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f"[{tag}] {config.name} ({format_time(build_time)}) {size_str}")
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
if dest_path:
|
if dest_path:
|
||||||
print(f" -> {dest_path}")
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
if not all_ok:
|
if not all_ok:
|
||||||
copy_out = copy_buf.getvalue()
|
copy_out = copy_buf.getvalue()
|
||||||
if copy_out.strip():
|
if copy_out.strip():
|
||||||
print(copy_out.rstrip())
|
print(copy_out.rstrip())
|
||||||
if captured.strip():
|
if captured.strip():
|
||||||
print("--- Build output ---")
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
print(captured.rstrip())
|
print(captured.rstrip())
|
||||||
|
|
||||||
with idx_lock:
|
with idx_lock:
|
||||||
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=n) as executor:
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
for fut in as_completed(futures):
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
exc = fut.exception()
|
for fut in as_completed(futures):
|
||||||
if exc:
|
exc = fut.exception()
|
||||||
idx = futures[fut]
|
if exc:
|
||||||
with _print_lock:
|
idx = futures[fut]
|
||||||
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
with _print_lock:
|
||||||
with idx_lock:
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
wall_time = time.time() - total_start
|
wall_time = time.time() - total_start
|
||||||
print(f"\nAll {n} configs finished in {format_time(wall_time)} (wall clock)")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
return [results_by_idx[i] for i in range(n)]
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -722,15 +789,15 @@ Examples:
|
|||||||
configs_to_build = [CONFIGS[name] for name in args.configs]
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Building nsis7z plugin - {len(configs_to_build)} configuration(s)")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin{Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Visual Studio: {vs_version_name} (toolset {platform_toolset})")
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
print(f"MSBuild: {msbuild_path}")
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
print(f"Project: {project_file}")
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
print(f"Plugins: {plugins_dir}")
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
print(f"Rebuild: {args.rebuild}")
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
print(f"Verbosity: {args.verbosity}")
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Print CPU and parallel build info
|
# Print CPU and parallel build info
|
||||||
@@ -824,12 +891,13 @@ Examples:
|
|||||||
total_time = time.time() - total_start_time
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
print("\n" + "=" * 50)
|
print()
|
||||||
all_success = all(success for _, success, _, _, _ in build_results)
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
print("ALL BUILDS SUCCESSFUL!")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("\nPlugins copied to:")
|
print("\nPlugins copied to:")
|
||||||
for config, _, build_time, file_size, dest_path in build_results:
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
@@ -840,22 +908,24 @@ Examples:
|
|||||||
print()
|
print()
|
||||||
clean_build_artifacts(project_dir, configs_to_build)
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
else:
|
else:
|
||||||
print("SOME BUILDS FAILED!")
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("\nFailed configurations:")
|
print("\nFailed configurations:")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
if not success:
|
if not success:
|
||||||
print(f" - {config.name}")
|
print(f" - {config.name}")
|
||||||
|
|
||||||
# Show timing summary
|
# Show timing summary
|
||||||
print("\n" + "-" * 50)
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print("Build Summary:")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
status = "OK" if success else "FAIL"
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f" {status} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
print("-" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print(f"Total time: {format_time(total_time)}")
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Pause if requested
|
# Pause if requested
|
||||||
|
|||||||
@@ -21,6 +21,69 @@ from typing import List, Tuple, Optional
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
class BuildConfig:
|
class BuildConfig:
|
||||||
"""Configuration for a single build target"""
|
"""Configuration for a single build target"""
|
||||||
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
@@ -84,6 +147,7 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
str(_VSWHERE_PATH),
|
str(_VSWHERE_PATH),
|
||||||
|
'-products', '*',
|
||||||
'-version', _VS_VERSION_RANGE[ver],
|
'-version', _VS_VERSION_RANGE[ver],
|
||||||
'-latest',
|
'-latest',
|
||||||
'-requires', 'Microsoft.Component.MSBuild',
|
'-requires', 'Microsoft.Component.MSBuild',
|
||||||
@@ -102,6 +166,13 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Numeric version folder used by VS2026 installer (version 18.x)
|
||||||
|
_VS_NUMERIC_FOLDER = {
|
||||||
|
'2026': '18',
|
||||||
|
'2022': '2022',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
||||||
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
||||||
|
|
||||||
@@ -114,11 +185,16 @@ def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
|||||||
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
||||||
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
for version in versions_to_try:
|
for version in versions_to_try:
|
||||||
base_path = Path(rf'C:\Program Files\Microsoft Visual Studio\{version}')
|
numeric = _VS_NUMERIC_FOLDER.get(version, version)
|
||||||
for edition in vs_editions:
|
base_paths = [
|
||||||
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
Path(rf'C:\Program Files (x86)\Microsoft Visual Studio\{numeric}'),
|
||||||
if msbuild_path.exists():
|
Path(rf'C:\Program Files\Microsoft Visual Studio\{version}'),
|
||||||
return msbuild_path, _VS_TOOLSET[version], version
|
]
|
||||||
|
for base_path in base_paths:
|
||||||
|
for edition in vs_editions:
|
||||||
|
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
||||||
|
if msbuild_path.exists():
|
||||||
|
return msbuild_path, _VS_TOOLSET[version], version
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_project_paths() -> Tuple[Path, Path, Path]:
|
def get_project_paths() -> Tuple[Path, Path, Path]:
|
||||||
@@ -209,41 +285,41 @@ def get_memory_optimizations() -> List[str]:
|
|||||||
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
"""Print CPU information for build optimization"""
|
"""Print CPU information for build optimization"""
|
||||||
if not use_parallel:
|
if not use_parallel:
|
||||||
print("Build mode: Single-threaded")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
return
|
return
|
||||||
|
|
||||||
cpu_info = get_cpu_info()
|
cpu_info = get_cpu_info()
|
||||||
optimal_threads = get_optimal_thread_count()
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
print("Build mode: Parallel")
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
print(f"Logical cores: {cpu_info['logical_cores']}")
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
print(f"Physical cores: {cpu_info['physical_cores']}")
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
if cpu_info['has_hyperthreading']:
|
if cpu_info['has_hyperthreading']:
|
||||||
print("Hyperthreading: ENABLED")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads} (using physical cores)")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
else:
|
else:
|
||||||
print("Hyperthreading: NOT AVAILABLE")
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
print(f"Optimal threads: {optimal_threads}")
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"MSBuild threads: {optimal_threads}")
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
if use_optimizations:
|
if use_optimizations:
|
||||||
print("Optimizations: ENABLED (parallel, memory, caching)")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
print(f"Available memory: {memory_gb:.1f} GB")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Available memory: Unknown (install psutil for details)")
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print("Optimizations: DISABLED")
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
if not cpu_info['has_psutil']:
|
if not cpu_info['has_psutil']:
|
||||||
print("Note: Install 'psutil' for detailed CPU/memory info (pip install psutil)")
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
@@ -297,12 +373,12 @@ def build_configuration(
|
|||||||
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
cmd.append(f'/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
if not capture_output:
|
if not capture_output:
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
if counter:
|
if counter:
|
||||||
print(f"Building {config.name} [{counter}]")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
else:
|
else:
|
||||||
print(f"Building {config.name}...")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
print('='*50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
# Execute build and measure time
|
# Execute build and measure time
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -340,7 +416,7 @@ def copy_output(
|
|||||||
dest_dir = plugins_dir / config.dest_dir
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
if not output_file.exists():
|
if not output_file.exists():
|
||||||
print(f"ERROR: {config.name} DLL not found at {output_file}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
# Create destination directory
|
# Create destination directory
|
||||||
@@ -351,16 +427,16 @@ def copy_output(
|
|||||||
dest_path = dest_dir / 'nsis7z.dll'
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
shutil.copy2(output_file, dest_path)
|
shutil.copy2(output_file, dest_path)
|
||||||
file_size = output_file.stat().st_size
|
file_size = output_file.stat().st_size
|
||||||
print(f"SUCCESS: {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
return True, file_size, dest_path
|
return True, file_size, dest_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to copy {config.name}: {e}")
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
return False, 0, None
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
"""Clean up build artifacts"""
|
"""Clean up build artifacts"""
|
||||||
print("\nCleaning build artifacts...")
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
build_base_dir = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
|
||||||
@@ -374,11 +450,11 @@ def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None
|
|||||||
if dir_path.exists():
|
if dir_path.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
print(f" - Cleaned: {dir_path.name}")
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" - Failed to clean {dir_path.name}: {e}")
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
print("Build artifacts cleaned successfully.")
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def format_time(seconds: float) -> str:
|
def format_time(seconds: float) -> str:
|
||||||
@@ -513,8 +589,8 @@ def _build_configs_parallel(
|
|||||||
in the same order as *configs*.
|
in the same order as *configs*.
|
||||||
"""
|
"""
|
||||||
n = len(configs)
|
n = len(configs)
|
||||||
print(f"\nParallel-configs: launching {n} builds simultaneously...")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
total_start = time.time()
|
total_start = time.time()
|
||||||
results_by_idx: dict = {}
|
results_by_idx: dict = {}
|
||||||
@@ -538,35 +614,39 @@ def _build_configs_parallel(
|
|||||||
|
|
||||||
with _print_lock:
|
with _print_lock:
|
||||||
all_ok = success and copy_ok
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
tag = "OK" if all_ok else "FAILED"
|
tag = "OK" if all_ok else "FAILED"
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f"[{tag}] {config.name} ({format_time(build_time)}) {size_str}")
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
if dest_path:
|
if dest_path:
|
||||||
print(f" -> {dest_path}")
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
if not all_ok:
|
if not all_ok:
|
||||||
copy_out = copy_buf.getvalue()
|
copy_out = copy_buf.getvalue()
|
||||||
if copy_out.strip():
|
if copy_out.strip():
|
||||||
print(copy_out.rstrip())
|
print(copy_out.rstrip())
|
||||||
if captured.strip():
|
if captured.strip():
|
||||||
print("--- Build output ---")
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
print(captured.rstrip())
|
print(captured.rstrip())
|
||||||
|
|
||||||
with idx_lock:
|
with idx_lock:
|
||||||
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=n) as executor:
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
for fut in as_completed(futures):
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
exc = fut.exception()
|
for fut in as_completed(futures):
|
||||||
if exc:
|
exc = fut.exception()
|
||||||
idx = futures[fut]
|
if exc:
|
||||||
with _print_lock:
|
idx = futures[fut]
|
||||||
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
with _print_lock:
|
||||||
with idx_lock:
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
wall_time = time.time() - total_start
|
wall_time = time.time() - total_start
|
||||||
print(f"\nAll {n} configs finished in {format_time(wall_time)} (wall clock)")
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
return [results_by_idx[i] for i in range(n)]
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -736,15 +816,15 @@ Examples:
|
|||||||
configs_to_build = [CONFIGS[name] for name in args.configs]
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Building nsis7z plugin (VS2026) - {len(configs_to_build)} configuration(s)")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin (VS2026){Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
print("=" * 60)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
print(f"Visual Studio: {vs_version_name} (toolset {platform_toolset})")
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
print(f"MSBuild: {msbuild_path}")
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
print(f"Project: {project_file}")
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
print(f"Plugins: {plugins_dir}")
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
print(f"Rebuild: {args.rebuild}")
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
print(f"Verbosity: {args.verbosity}")
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Print CPU and parallel build info
|
# Print CPU and parallel build info
|
||||||
@@ -838,12 +918,13 @@ Examples:
|
|||||||
total_time = time.time() - total_start_time
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
print("\n" + "=" * 50)
|
print()
|
||||||
all_success = all(success for _, success, _, _, _ in build_results)
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
print("ALL BUILDS SUCCESSFUL!")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
print("\nPlugins copied to:")
|
print("\nPlugins copied to:")
|
||||||
for config, _, build_time, file_size, dest_path in build_results:
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
dest = dest_path if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
@@ -854,22 +935,24 @@ Examples:
|
|||||||
print()
|
print()
|
||||||
clean_build_artifacts(project_dir, configs_to_build)
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
else:
|
else:
|
||||||
print("SOME BUILDS FAILED!")
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("=" * 50)
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
print("\nFailed configurations:")
|
print("\nFailed configurations:")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
if not success:
|
if not success:
|
||||||
print(f" - {config.name}")
|
print(f" - {config.name}")
|
||||||
|
|
||||||
# Show timing summary
|
# Show timing summary
|
||||||
print("\n" + "-" * 50)
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print("Build Summary:")
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
for config, success, build_time, file_size, dest_path in build_results:
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
status = "OK" if success else "FAIL"
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
print(f" {status} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
print("-" * 50)
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
print(f"Total time: {format_time(total_time)}")
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Pause if requested
|
# Pause if requested
|
||||||
|
|||||||
@@ -0,0 +1,767 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build script for nsis7z plugin (7-Zip ZS / mcmilk/7-Zip-zstd) - Visual Studio 2026
|
||||||
|
Supports multiple build configurations with flexible parameters.
|
||||||
|
Uses PlatformToolset v145 (Visual Studio 2026 Build Tools).
|
||||||
|
|
||||||
|
The 7-Zip ZS source is pulled from the git submodule at versions/7-zip-zstd/.
|
||||||
|
The NSIS plugin wrapper (nsis7z.cpp, vcxproj, …) lives in versions/zstd/.
|
||||||
|
|
||||||
|
NOTE: VS2026 Build Tools use v145 toolset (version 18.x)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
|
import multiprocessing
|
||||||
|
import threading
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
class BuildConfig:
|
||||||
|
"""Configuration for a single build target"""
|
||||||
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
|
self.name = name
|
||||||
|
self.config = config
|
||||||
|
self.platform = platform
|
||||||
|
self.dest_dir = dest_dir
|
||||||
|
|
||||||
|
|
||||||
|
# Predefined build configurations
|
||||||
|
CONFIGS = {
|
||||||
|
'x86-unicode': BuildConfig(
|
||||||
|
name='x86-unicode',
|
||||||
|
config='Release Unicode',
|
||||||
|
platform='Win32',
|
||||||
|
dest_dir='x86-unicode'
|
||||||
|
),
|
||||||
|
'x64-unicode': BuildConfig(
|
||||||
|
name='x64-unicode',
|
||||||
|
config='Release Unicode',
|
||||||
|
platform='x64',
|
||||||
|
dest_dir='amd64-unicode'
|
||||||
|
),
|
||||||
|
'x86-ansi': BuildConfig(
|
||||||
|
name='x86-ansi',
|
||||||
|
config='Release',
|
||||||
|
platform='Win32',
|
||||||
|
dest_dir='x86-ansi'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Standard vswhere.exe location (installed with any VS 2017+ setup)
|
||||||
|
_VSWHERE_PATH = Path(r'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe')
|
||||||
|
|
||||||
|
# vswhere version range per VS year
|
||||||
|
_VS_VERSION_RANGE = {
|
||||||
|
'2026': '[18.0,19.0)',
|
||||||
|
'2022': '[17.0,18.0)',
|
||||||
|
}
|
||||||
|
|
||||||
|
# MSBuild toolset per VS year
|
||||||
|
_VS_TOOLSET = {
|
||||||
|
'2026': 'v145', # VS 2026 uses v145 toolset
|
||||||
|
'2022': 'v143', # VS 2022 uses v143 toolset
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str]]':
|
||||||
|
"""Locate MSBuild via vswhere.exe."""
|
||||||
|
if not _VSWHERE_PATH.exists():
|
||||||
|
return None
|
||||||
|
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
|
for ver in versions_to_try:
|
||||||
|
if ver not in _VS_VERSION_RANGE:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
str(_VSWHERE_PATH),
|
||||||
|
'-products', '*',
|
||||||
|
'-version', _VS_VERSION_RANGE[ver],
|
||||||
|
'-latest',
|
||||||
|
'-requires', 'Microsoft.Component.MSBuild',
|
||||||
|
'-find', r'MSBuild\**\Bin\MSBuild.exe',
|
||||||
|
],
|
||||||
|
capture_output=True, text=True, timeout=15,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
lines = [ln.strip() for ln in result.stdout.splitlines() if ln.strip()]
|
||||||
|
if lines:
|
||||||
|
msbuild_path = Path(lines[0])
|
||||||
|
if msbuild_path.exists():
|
||||||
|
return msbuild_path, _VS_TOOLSET[ver], ver
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Numeric version folder used by VS2026 installer (version 18.x)
|
||||||
|
_VS_NUMERIC_FOLDER = {
|
||||||
|
'2026': '18',
|
||||||
|
'2022': '2022',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def find_msbuild(vs_version: str = 'auto') -> 'Optional[Tuple[Path, str, str]]':
|
||||||
|
"""Find MSBuild.exe via vswhere, then well-known fallback paths.
|
||||||
|
|
||||||
|
Returns (msbuild_path, toolset_version, vs_year) or None.
|
||||||
|
"""
|
||||||
|
result = _find_msbuild_via_vswhere(vs_version)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
|
||||||
|
vs_editions = ['Community', 'Professional', 'Enterprise', 'BuildTools']
|
||||||
|
versions_to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
|
for version in versions_to_try:
|
||||||
|
numeric = _VS_NUMERIC_FOLDER.get(version, version)
|
||||||
|
base_paths = [
|
||||||
|
Path(rf'C:\Program Files (x86)\Microsoft Visual Studio\{numeric}'),
|
||||||
|
Path(rf'C:\Program Files\Microsoft Visual Studio\{version}'),
|
||||||
|
]
|
||||||
|
for base_path in base_paths:
|
||||||
|
for edition in vs_editions:
|
||||||
|
msbuild_path = base_path / edition / 'MSBuild' / 'Current' / 'Bin' / 'MSBuild.exe'
|
||||||
|
if msbuild_path.exists():
|
||||||
|
return msbuild_path, _VS_TOOLSET[version], version
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_paths(toolset: str = 'v145') -> Tuple[Path, Path, Path]:
|
||||||
|
"""Get project directory, project file, and plugins directory"""
|
||||||
|
script_dir = Path(__file__).parent.absolute()
|
||||||
|
project_dir = script_dir.parent.parent / 'versions' / 'zstd'
|
||||||
|
vcxproj = 'Nsis7z_vs2026.vcxproj'
|
||||||
|
project_file = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / vcxproj
|
||||||
|
plugins_dir = script_dir.parent.parent / 'plugins'
|
||||||
|
|
||||||
|
return project_dir, project_file, plugins_dir
|
||||||
|
|
||||||
|
|
||||||
|
def get_cpu_info() -> dict:
|
||||||
|
"""Get CPU information for parallel build optimization"""
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
logical_cores = psutil.cpu_count(logical=True)
|
||||||
|
physical_cores = psutil.cpu_count(logical=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'logical_cores': logical_cores,
|
||||||
|
'physical_cores': physical_cores,
|
||||||
|
'has_hyperthreading': logical_cores > physical_cores,
|
||||||
|
'has_psutil': True
|
||||||
|
}
|
||||||
|
except ImportError:
|
||||||
|
cpu_count = multiprocessing.cpu_count()
|
||||||
|
return {
|
||||||
|
'logical_cores': cpu_count,
|
||||||
|
'physical_cores': cpu_count,
|
||||||
|
'has_hyperthreading': False,
|
||||||
|
'has_psutil': False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_optimal_thread_count() -> int:
|
||||||
|
"""Get optimal thread count for compilation"""
|
||||||
|
cpu_info = get_cpu_info()
|
||||||
|
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||||
|
return cpu_info['physical_cores']
|
||||||
|
return cpu_info['logical_cores']
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_optimizations() -> List[str]:
|
||||||
|
"""Get additional build optimization flags"""
|
||||||
|
return [
|
||||||
|
'/p:BuildInParallel=true',
|
||||||
|
'/p:MultiProcessorCompilation=true',
|
||||||
|
'/p:PreferredToolArchitecture=x64',
|
||||||
|
'/p:UseSharedCompilation=true',
|
||||||
|
'/p:TrackFileAccess=false',
|
||||||
|
'/nodeReuse:true',
|
||||||
|
'/p:GenerateResourceUsePreserializedResources=true'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_optimizations() -> List[str]:
|
||||||
|
"""Get memory optimization flags for faster builds"""
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
memory_gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
if memory_gb >= 16:
|
||||||
|
return [
|
||||||
|
'/p:DisableFastUpToDateCheck=false',
|
||||||
|
'/p:BuildProjectReferences=true',
|
||||||
|
'/p:UseCommonOutputDirectory=false'
|
||||||
|
]
|
||||||
|
elif memory_gb >= 8:
|
||||||
|
return ['/p:DisableFastUpToDateCheck=false']
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
except ImportError:
|
||||||
|
return ['/p:DisableFastUpToDateCheck=false']
|
||||||
|
|
||||||
|
|
||||||
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
|
"""Print CPU information for build optimization"""
|
||||||
|
if not use_parallel:
|
||||||
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Single-threaded{Colors.RESET}")
|
||||||
|
if use_optimizations:
|
||||||
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_WHITE}ENABLED{Colors.RESET} (memory, caching)")
|
||||||
|
return
|
||||||
|
|
||||||
|
cpu_info = get_cpu_info()
|
||||||
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
|
||||||
|
print(f"{Colors.CYAN}Build mode:{Colors.RESET} {Colors.BRIGHT_WHITE}Parallel{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Logical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['logical_cores']}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Physical cores:{Colors.RESET} {Colors.BRIGHT_WHITE}{cpu_info['physical_cores']}{Colors.RESET}")
|
||||||
|
|
||||||
|
if cpu_info['has_hyperthreading']:
|
||||||
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET} (using physical cores)")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.CYAN}Hyperthreading:{Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Optimal threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
|
print(f"{Colors.CYAN}MSBuild threads:{Colors.RESET} {Colors.BRIGHT_WHITE}{optimal_threads}{Colors.RESET}")
|
||||||
|
|
||||||
|
if use_optimizations:
|
||||||
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} (parallel, memory, caching)")
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
memory_gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.BRIGHT_WHITE}{memory_gb:.1f} GB{Colors.RESET}")
|
||||||
|
except ImportError:
|
||||||
|
print(f"{Colors.CYAN}Available memory:{Colors.RESET} {Colors.GRAY}Unknown (install psutil for details){Colors.RESET}")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.CYAN}Optimizations:{Colors.RESET} {Colors.RED}DISABLED{Colors.RESET}")
|
||||||
|
|
||||||
|
if not cpu_info['has_psutil']:
|
||||||
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def build_configuration(
|
||||||
|
msbuild_path: Path,
|
||||||
|
project_file: Path,
|
||||||
|
config: BuildConfig,
|
||||||
|
rebuild: bool = True,
|
||||||
|
verbosity: str = 'quiet',
|
||||||
|
parallel: bool = True,
|
||||||
|
optimizations: bool = True,
|
||||||
|
counter: str = "",
|
||||||
|
capture_output: bool = False,
|
||||||
|
toolset: str = 'v145',
|
||||||
|
) -> Tuple[bool, float, str]:
|
||||||
|
"""Build a single configuration.
|
||||||
|
|
||||||
|
Returns (success, elapsed_time, captured_output).
|
||||||
|
"""
|
||||||
|
cmd = [
|
||||||
|
str(msbuild_path),
|
||||||
|
str(project_file),
|
||||||
|
f'/t:{"Rebuild" if rebuild else "Build"}',
|
||||||
|
f'/p:Configuration={config.config}',
|
||||||
|
f'/p:Platform={config.platform}',
|
||||||
|
f'/p:OutDir=Build\\{config.name}\\',
|
||||||
|
f'/p:IntDir=Build\\{config.name}\\obj\\',
|
||||||
|
'/p:WindowsTargetPlatformVersion=10.0',
|
||||||
|
f'/p:PlatformToolset={toolset}',
|
||||||
|
f'/v:{verbosity}',
|
||||||
|
]
|
||||||
|
|
||||||
|
if parallel:
|
||||||
|
optimal_threads = get_optimal_thread_count()
|
||||||
|
cmd.extend([
|
||||||
|
f'/maxcpucount:{optimal_threads}',
|
||||||
|
'/p:UseMultiToolTask=true',
|
||||||
|
f'/p:CL_MPCount={optimal_threads}'
|
||||||
|
])
|
||||||
|
|
||||||
|
if optimizations:
|
||||||
|
cmd.extend(get_build_optimizations())
|
||||||
|
cmd.extend(get_memory_optimizations())
|
||||||
|
cache_dir = setup_build_cache()
|
||||||
|
if cache_dir:
|
||||||
|
cmd.append('/p:MSBuildCacheEnabled=true')
|
||||||
|
|
||||||
|
if not capture_output:
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'=' * 50}{Colors.RESET}")
|
||||||
|
if counter:
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name} [{counter}]{Colors.RESET}")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_WHITE}Building {config.name}...{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_YELLOW}{'=' * 50}{Colors.RESET}")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
try:
|
||||||
|
if capture_output:
|
||||||
|
result = subprocess.run(cmd, check=False, capture_output=True, text=True)
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
return result.returncode == 0, elapsed, result.stdout + result.stderr
|
||||||
|
else:
|
||||||
|
result = subprocess.run(cmd, check=False)
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
return result.returncode == 0, elapsed, ""
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
msg = f"ERROR: Build failed with exception: {e}"
|
||||||
|
if capture_output:
|
||||||
|
return False, elapsed, msg
|
||||||
|
print(msg)
|
||||||
|
return False, elapsed, ""
|
||||||
|
|
||||||
|
|
||||||
|
def copy_output(
|
||||||
|
project_dir: Path,
|
||||||
|
plugins_dir: Path,
|
||||||
|
config: BuildConfig
|
||||||
|
) -> Tuple[bool, int, Optional[Path]]:
|
||||||
|
"""Copy built DLL to destination directory.
|
||||||
|
|
||||||
|
Returns (success, file_size, dest_path).
|
||||||
|
"""
|
||||||
|
output_file = (
|
||||||
|
project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z'
|
||||||
|
/ 'Build' / config.name / 'nsis7z.dll'
|
||||||
|
)
|
||||||
|
dest_dir = plugins_dir / config.dest_dir
|
||||||
|
|
||||||
|
if not output_file.exists():
|
||||||
|
print(f"{Colors.RED}ERROR:{Colors.RESET} {config.name} DLL not found at {output_file}")
|
||||||
|
return False, 0, None
|
||||||
|
|
||||||
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest_path = dest_dir / 'nsis7z.dll'
|
||||||
|
shutil.copy2(output_file, dest_path)
|
||||||
|
file_size = output_file.stat().st_size
|
||||||
|
print(f"{Colors.BRIGHT_GREEN}SUCCESS:{Colors.RESET} {config.name} - {file_size:,} bytes - Copied to {dest_dir}")
|
||||||
|
return True, file_size, dest_path
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Colors.RED}ERROR:{Colors.RESET} Failed to copy {config.name}: {e}")
|
||||||
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
|
"""Clean up build artifacts"""
|
||||||
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
|
||||||
|
build_base_dir = (
|
||||||
|
project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Build'
|
||||||
|
)
|
||||||
|
|
||||||
|
dirs_to_clean = {build_base_dir / config.name for config in configs}
|
||||||
|
|
||||||
|
for dir_path in dirs_to_clean:
|
||||||
|
if dir_path.exists():
|
||||||
|
try:
|
||||||
|
shutil.rmtree(dir_path)
|
||||||
|
print(f" {Colors.GRAY}- Cleaned: {dir_path.name}{Colors.RESET}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {Colors.RED}- Failed to clean {dir_path.name}: {e}{Colors.RESET}")
|
||||||
|
|
||||||
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def format_time(seconds: float) -> str:
|
||||||
|
"""Format seconds to human-readable string"""
|
||||||
|
if seconds < 60:
|
||||||
|
return f"{seconds:.2f}s"
|
||||||
|
elif seconds < 3600:
|
||||||
|
minutes = int(seconds // 60)
|
||||||
|
secs = seconds % 60
|
||||||
|
return f"{minutes}m {secs:.1f}s"
|
||||||
|
else:
|
||||||
|
hours = int(seconds // 3600)
|
||||||
|
minutes = int((seconds % 3600) // 60)
|
||||||
|
secs = seconds % 60
|
||||||
|
return f"{hours}h {minutes}m {secs:.0f}s"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_build_cache() -> Optional[str]:
|
||||||
|
"""Setup build cache directory for faster incremental builds"""
|
||||||
|
try:
|
||||||
|
cache_dir = Path.home() / '.msbuild_cache' / 'nsis7z-zstd-vs2026'
|
||||||
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return str(cache_dir)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||||
|
"""Parse .vcxproj file to get available configurations"""
|
||||||
|
try:
|
||||||
|
tree = ET.parse(project_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ms': 'http://schemas.microsoft.com/developer/msbuild/2003'}
|
||||||
|
configs = []
|
||||||
|
for item_group in root.findall('.//ms:ItemGroup', ns):
|
||||||
|
for proj_config in item_group.findall('ms:ProjectConfiguration', ns):
|
||||||
|
include = proj_config.get('Include')
|
||||||
|
if include:
|
||||||
|
parts = include.split('|')
|
||||||
|
if len(parts) == 2:
|
||||||
|
configs.append((parts[0], parts[1]))
|
||||||
|
return configs
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not parse project file: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def print_available_configurations(project_file: Path) -> None:
|
||||||
|
"""Print all configurations available in the project"""
|
||||||
|
configs = get_available_configurations(project_file)
|
||||||
|
if not configs:
|
||||||
|
print("No configurations found or could not read project file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Available configurations in project:")
|
||||||
|
print("-" * 60)
|
||||||
|
from collections import defaultdict
|
||||||
|
grouped = defaultdict(list)
|
||||||
|
for config, platform in configs:
|
||||||
|
grouped[config].append(platform)
|
||||||
|
for config_name in sorted(grouped.keys()):
|
||||||
|
platforms = ', '.join(sorted(grouped[config_name]))
|
||||||
|
print(f" {config_name:25s} - {platforms}")
|
||||||
|
print(f"\nTotal: {len(configs)} configuration(s)")
|
||||||
|
print()
|
||||||
|
print("Configurations mapped in this build script:")
|
||||||
|
print("-" * 60)
|
||||||
|
for name, config in CONFIGS.items():
|
||||||
|
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
_print_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_configs_parallel(
|
||||||
|
msbuild_path: Path,
|
||||||
|
project_file: Path,
|
||||||
|
configs: 'List[BuildConfig]',
|
||||||
|
*,
|
||||||
|
rebuild: bool,
|
||||||
|
verbosity: str,
|
||||||
|
parallel: bool,
|
||||||
|
optimizations: bool,
|
||||||
|
project_dir: Path,
|
||||||
|
plugins_dir: Path,
|
||||||
|
toolset: str = 'v145',
|
||||||
|
) -> list:
|
||||||
|
"""Build all configurations simultaneously, printing output atomically."""
|
||||||
|
n = len(configs)
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}Parallel-configs:{Colors.RESET} launching {Colors.BRIGHT_WHITE}{n}{Colors.RESET} builds simultaneously...")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
|
total_start = time.time()
|
||||||
|
results_by_idx: dict = {}
|
||||||
|
idx_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _build_one(idx: int, config: 'BuildConfig'):
|
||||||
|
success, build_time, captured = build_configuration(
|
||||||
|
msbuild_path, project_file, config,
|
||||||
|
rebuild=rebuild, verbosity=verbosity,
|
||||||
|
parallel=parallel, optimizations=optimizations,
|
||||||
|
capture_output=True, toolset=toolset,
|
||||||
|
)
|
||||||
|
import io, contextlib
|
||||||
|
copy_buf = io.StringIO()
|
||||||
|
if success:
|
||||||
|
with contextlib.redirect_stdout(copy_buf):
|
||||||
|
copy_ok, file_size, dest_path = copy_output(project_dir, plugins_dir, config)
|
||||||
|
else:
|
||||||
|
copy_ok, file_size, dest_path = False, 0, None
|
||||||
|
|
||||||
|
with _print_lock:
|
||||||
|
all_ok = success and copy_ok
|
||||||
|
tag_color = Colors.BRIGHT_GREEN if all_ok else Colors.RED
|
||||||
|
tag = "OK" if all_ok else "FAILED"
|
||||||
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(f"{tag_color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
|
if dest_path:
|
||||||
|
print(f" {Colors.GRAY}-> {dest_path}{Colors.RESET}")
|
||||||
|
if not all_ok:
|
||||||
|
copy_out = copy_buf.getvalue()
|
||||||
|
if copy_out.strip():
|
||||||
|
print(copy_out.rstrip())
|
||||||
|
if captured.strip():
|
||||||
|
print(f"{Colors.YELLOW}--- Build output ---{Colors.RESET}")
|
||||||
|
print(captured.rstrip())
|
||||||
|
|
||||||
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
|
with Spinner(f"Building {n} configs in parallel...") as _spinner:
|
||||||
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
|
for fut in as_completed(futures):
|
||||||
|
exc = fut.exception()
|
||||||
|
if exc:
|
||||||
|
idx = futures[fut]
|
||||||
|
with _print_lock:
|
||||||
|
print(f"{Colors.RED}ERROR:{Colors.RESET} worker for config index {idx} raised: {exc}")
|
||||||
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
|
wall_time = time.time() - total_start
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}All {n} configs finished in {Colors.BRIGHT_WHITE}{format_time(wall_time)}{Colors.CYAN} (wall clock){Colors.RESET}")
|
||||||
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main build function"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Build nsis7z plugin (7-Zip ZS / mcmilk/7-Zip-zstd, Visual Studio 2026)',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
%(prog)s # Build all configurations
|
||||||
|
%(prog)s --list # List script configurations
|
||||||
|
%(prog)s --list-project # List all project configurations
|
||||||
|
%(prog)s --configs x86-unicode # Build only x86-unicode
|
||||||
|
%(prog)s --configs x86-unicode x64-unicode
|
||||||
|
%(prog)s --no-rebuild # Incremental build
|
||||||
|
%(prog)s --no-parallel # Single-threaded build
|
||||||
|
%(prog)s --no-clean # Skip cleanup
|
||||||
|
%(prog)s --verbosity minimal # Show more build output
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--configs', nargs='+',
|
||||||
|
choices=list(CONFIGS.keys()) + ['all'],
|
||||||
|
default=['all'],
|
||||||
|
help='Configurations to build (default: all)'
|
||||||
|
)
|
||||||
|
parser.add_argument('--rebuild', action='store_true', default=True)
|
||||||
|
parser.add_argument('--no-rebuild', action='store_false', dest='rebuild',
|
||||||
|
help='Incremental build (only changed files)')
|
||||||
|
parser.add_argument('--parallel', action='store_true', default=True)
|
||||||
|
parser.add_argument('--no-parallel', action='store_false', dest='parallel')
|
||||||
|
parser.add_argument('--verbosity',
|
||||||
|
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||||
|
default='quiet')
|
||||||
|
parser.add_argument('--clean', action='store_true', default=True)
|
||||||
|
parser.add_argument('--no-clean', action='store_false', dest='clean')
|
||||||
|
parser.add_argument('--list', action='store_true',
|
||||||
|
help='List script configurations and exit')
|
||||||
|
parser.add_argument('--list-project', action='store_true',
|
||||||
|
help='List all configurations in the .vcxproj and exit')
|
||||||
|
parser.add_argument('--pause', action='store_true',
|
||||||
|
help='Wait for key press at the end')
|
||||||
|
parser.add_argument('--no-optimizations', action='store_true',
|
||||||
|
help='Disable additional build optimizations')
|
||||||
|
parser.add_argument('--vs-version', choices=['auto', '2026', '2022'], default='auto',
|
||||||
|
help='Visual Studio version to use (default: auto)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Find MSBuild
|
||||||
|
msbuild_result = find_msbuild(args.vs_version)
|
||||||
|
if not msbuild_result:
|
||||||
|
print("ERROR: MSBuild not found!")
|
||||||
|
return 1
|
||||||
|
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||||
|
|
||||||
|
# Get project paths
|
||||||
|
project_dir, project_file, plugins_dir = get_project_paths(platform_toolset)
|
||||||
|
|
||||||
|
if args.list_project:
|
||||||
|
if not project_file.exists():
|
||||||
|
print(f"ERROR: Project file not found: {project_file}")
|
||||||
|
return 1
|
||||||
|
print_available_configurations(project_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
print("Build script configurations (7-Zip ZS / mcmilk, VS2026):")
|
||||||
|
print("-" * 60)
|
||||||
|
for name, config in CONFIGS.items():
|
||||||
|
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||||
|
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not project_file.exists():
|
||||||
|
print(f"ERROR: Project file not found: {project_file}")
|
||||||
|
print()
|
||||||
|
print("Make sure the submodule is initialised:")
|
||||||
|
print(" git submodule update --init versions/7-zip-zstd")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Determine configurations to build
|
||||||
|
if 'all' in args.configs:
|
||||||
|
configs_to_build = list(CONFIGS.values())
|
||||||
|
else:
|
||||||
|
configs_to_build = [CONFIGS[name] for name in args.configs]
|
||||||
|
|
||||||
|
# Print header
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Building nsis7z plugin (7-Zip ZS, VS2026){Colors.RESET} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} configuration(s)")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"="*60}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Visual Studio:{Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name} (toolset {platform_toolset}){Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}MSBuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Project:{Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Plugins:{Colors.RESET} {Colors.BRIGHT_WHITE}{plugins_dir}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Rebuild:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Verbosity:{Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
use_optimizations = not args.no_optimizations
|
||||||
|
print_cpu_info(args.parallel, use_optimizations)
|
||||||
|
|
||||||
|
# Build
|
||||||
|
build_results = []
|
||||||
|
total_start_time = time.time()
|
||||||
|
|
||||||
|
if args.parallel and len(configs_to_build) > 1:
|
||||||
|
build_results = _build_configs_parallel(
|
||||||
|
msbuild_path, project_file, configs_to_build,
|
||||||
|
rebuild=args.rebuild, verbosity=args.verbosity,
|
||||||
|
parallel=args.parallel, optimizations=use_optimizations,
|
||||||
|
project_dir=project_dir, plugins_dir=plugins_dir,
|
||||||
|
toolset=platform_toolset,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for i, config in enumerate(configs_to_build, 1):
|
||||||
|
success, build_time, _ = build_configuration(
|
||||||
|
msbuild_path, project_file, config,
|
||||||
|
rebuild=args.rebuild, verbosity=args.verbosity,
|
||||||
|
parallel=args.parallel, optimizations=use_optimizations,
|
||||||
|
counter=f"{i}/{len(configs_to_build)}",
|
||||||
|
toolset=platform_toolset,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print(f"ERROR: {config.name} build failed! (Time: {format_time(build_time)})")
|
||||||
|
build_results.append((config, False, build_time, 0, None))
|
||||||
|
continue
|
||||||
|
|
||||||
|
success, file_size, dest_path = copy_output(project_dir, plugins_dir, config)
|
||||||
|
print(f"Build time: {format_time(build_time)}")
|
||||||
|
build_results.append((config, success, build_time, file_size, dest_path))
|
||||||
|
|
||||||
|
total_time = time.time() - total_start_time
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print()
|
||||||
|
all_success = all(success for _, success, _, _, _ in build_results)
|
||||||
|
|
||||||
|
if all_success:
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}ALL BUILDS SUCCESSFUL!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_GREEN}{"="*50}{Colors.RESET}")
|
||||||
|
print("\nPlugins copied to:")
|
||||||
|
for config, _, build_time, file_size, dest_path in build_results:
|
||||||
|
dest = dest_path or plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||||
|
print(f" - {dest}")
|
||||||
|
if args.clean:
|
||||||
|
print()
|
||||||
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
|
else:
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}SOME BUILDS FAILED!{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.RED}{"="*50}{Colors.RESET}")
|
||||||
|
print("\nFailed configurations:")
|
||||||
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
|
if not success:
|
||||||
|
print(f" - {config.name}")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}Build Summary:{Colors.RESET}")
|
||||||
|
for config, success, build_time, file_size, dest_path in build_results:
|
||||||
|
status = "OK" if success else "FAIL"
|
||||||
|
row_color = Colors.BRIGHT_GREEN if success else Colors.RED
|
||||||
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
|
print(f" {row_color}{status}{Colors.RESET} {config.name:15s} - {format_time(build_time):8s} - {size_str}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{"-"*50}{Colors.RESET}")
|
||||||
|
print(f"Total time: {Colors.BRIGHT_WHITE}{format_time(total_time)}{Colors.RESET}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if args.pause:
|
||||||
|
try:
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
except EOFError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 0 if all_success else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Linux build path for nsis7z plugin.
|
||||||
|
|
||||||
|
Builds Windows DLL artifacts from Linux using MinGW-w64 cross compilers.
|
||||||
|
Current support: 7-Zip 26.00.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
SUPPORTED = {"26.00"}
|
||||||
|
|
||||||
|
CONFIGS = {
|
||||||
|
"x86-ansi": {
|
||||||
|
"triplet": "i686-w64-mingw32",
|
||||||
|
"is_x64": "0",
|
||||||
|
"unicode": False,
|
||||||
|
"dest": "plugins/x86-ansi/nsis7z.dll",
|
||||||
|
},
|
||||||
|
"x86-unicode": {
|
||||||
|
"triplet": "i686-w64-mingw32",
|
||||||
|
"is_x64": "0",
|
||||||
|
"unicode": True,
|
||||||
|
"dest": "plugins/x86-unicode/nsis7z.dll",
|
||||||
|
},
|
||||||
|
"x64-unicode": {
|
||||||
|
"triplet": "x86_64-w64-mingw32",
|
||||||
|
"is_x64": "1",
|
||||||
|
"unicode": True,
|
||||||
|
"dest": "plugins/amd64-unicode/nsis7z.dll",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _require_tool(name: str) -> None:
|
||||||
|
if shutil.which(name) is None:
|
||||||
|
print(f"ERROR: required tool not found: {name}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_jobs(value: str) -> int:
|
||||||
|
if value == "auto":
|
||||||
|
return max(1, os.cpu_count() or 1)
|
||||||
|
|
||||||
|
jobs = int(value)
|
||||||
|
if jobs < 1:
|
||||||
|
raise ValueError("jobs must be >= 1")
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
|
def _build_one(
|
||||||
|
zip_version: str,
|
||||||
|
cfg_name: str,
|
||||||
|
verbose: bool,
|
||||||
|
jobs: int,
|
||||||
|
clean: bool,
|
||||||
|
cleanup_artifacts: bool,
|
||||||
|
) -> int:
|
||||||
|
cfg = CONFIGS[cfg_name]
|
||||||
|
triplet = cfg["triplet"]
|
||||||
|
|
||||||
|
bundle_dir = ROOT / "versions" / zip_version / "CPP" / "7zip" / "Bundles" / "Nsis7z"
|
||||||
|
cpp_root = ROOT / "versions" / zip_version / "CPP"
|
||||||
|
makefile = bundle_dir / "makefile.gcc"
|
||||||
|
if not makefile.exists():
|
||||||
|
print(f"ERROR: Linux makefile not found: {makefile}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
out_obj = f"_o_{cfg_name.replace('-', '_')}"
|
||||||
|
local_flags = "-DNDEBUG"
|
||||||
|
if cfg["unicode"]:
|
||||||
|
local_flags += " -DUNICODE -D_UNICODE"
|
||||||
|
else:
|
||||||
|
# Precomp.h forces UNICODE=1 unconditionally; suppress it for the ansi
|
||||||
|
# build so that LPTSTR resolves to char* consistently in all TUs.
|
||||||
|
local_flags += " -DZ7_NO_UNICODE"
|
||||||
|
|
||||||
|
base_cmd = [
|
||||||
|
"make",
|
||||||
|
"-f",
|
||||||
|
"makefile.gcc",
|
||||||
|
f"O={out_obj}",
|
||||||
|
"SystemDrive=1",
|
||||||
|
"IS_MINGW=1",
|
||||||
|
"MSYSTEM=LINUX",
|
||||||
|
f"IS_X64={cfg['is_x64']}",
|
||||||
|
f"CC={triplet}-gcc",
|
||||||
|
f"CXX={triplet}-g++",
|
||||||
|
f"RC={triplet}-windres",
|
||||||
|
f"LOCAL_FLAGS_EXTRA={local_flags} -Wno-unknown-pragmas",
|
||||||
|
f"CXX_INCLUDE_FLAGS=-I{cpp_root}",
|
||||||
|
]
|
||||||
|
|
||||||
|
build_mode = "clean build" if clean else "incremental build"
|
||||||
|
print(f"[linux] Building {cfg_name} ({zip_version}, {build_mode}, -j{jobs})")
|
||||||
|
|
||||||
|
# Don't run 'clean' and 'all' in the same parallel make invocation:
|
||||||
|
# with -j, GNU make may execute them concurrently and remove objects while
|
||||||
|
# the linker is already consuming them.
|
||||||
|
if clean:
|
||||||
|
proc = subprocess.run(base_cmd + ["clean"], cwd=bundle_dir)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return proc.returncode
|
||||||
|
|
||||||
|
proc = subprocess.run(base_cmd + [f"-j{jobs}", "all"], cwd=bundle_dir)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return proc.returncode
|
||||||
|
|
||||||
|
built = bundle_dir / out_obj / "nsis7z.dll"
|
||||||
|
if not built.exists():
|
||||||
|
print(f"ERROR: expected artifact not found: {built}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
dest = ROOT / cfg["dest"]
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(built, dest)
|
||||||
|
|
||||||
|
if cleanup_artifacts:
|
||||||
|
shutil.rmtree(bundle_dir / out_obj, ignore_errors=True)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"[linux] Copied -> {dest}")
|
||||||
|
if cleanup_artifacts:
|
||||||
|
print(f"[linux] Cleaned build artifacts -> {bundle_dir / out_obj}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Linux build path for nsis7z")
|
||||||
|
parser.add_argument(
|
||||||
|
"--7zip-version",
|
||||||
|
dest="zip_version",
|
||||||
|
choices=["19.00", "25.01", "26.00", "zstd"],
|
||||||
|
default="26.00",
|
||||||
|
help="7-Zip version to build",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--configs",
|
||||||
|
nargs="+",
|
||||||
|
choices=list(CONFIGS.keys()) + ["all"],
|
||||||
|
default=["all"],
|
||||||
|
help="Build configuration(s): x86-ansi, x86-unicode, x64-unicode, or all",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--jobs",
|
||||||
|
default="auto",
|
||||||
|
help="Parallel make jobs. Use an integer or 'auto' (default: auto)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--clean",
|
||||||
|
action="store_true",
|
||||||
|
default=True,
|
||||||
|
help="Run 'make clean' before building (default: True)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-clean",
|
||||||
|
action="store_false",
|
||||||
|
dest="clean",
|
||||||
|
help="Skip 'make clean' to enable incremental rebuilds",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--cleanup-artifacts",
|
||||||
|
action="store_true",
|
||||||
|
default=True,
|
||||||
|
help="Remove per-config build artifact directories after successful copy (default: True)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-cleanup-artifacts",
|
||||||
|
action="store_false",
|
||||||
|
dest="cleanup_artifacts",
|
||||||
|
help="Keep per-config build artifact directories (_o_*)",
|
||||||
|
)
|
||||||
|
parser.add_argument("--verbose", action="store_true", help="Verbose output")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.zip_version not in SUPPORTED:
|
||||||
|
print(
|
||||||
|
"ERROR: Linux local build is currently supported only for 26.00. "
|
||||||
|
f"Requested: {args.zip_version}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
_require_tool("make")
|
||||||
|
for cfg in CONFIGS.values():
|
||||||
|
_require_tool(f"{cfg['triplet']}-gcc")
|
||||||
|
_require_tool(f"{cfg['triplet']}-g++")
|
||||||
|
_require_tool(f"{cfg['triplet']}-windres")
|
||||||
|
|
||||||
|
try:
|
||||||
|
jobs = _resolve_jobs(args.jobs)
|
||||||
|
except ValueError as exc:
|
||||||
|
print(f"ERROR: invalid --jobs value: {exc}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
wanted = list(CONFIGS.keys()) if "all" in args.configs else args.configs
|
||||||
|
|
||||||
|
for cfg_name in wanted:
|
||||||
|
code = _build_one(
|
||||||
|
args.zip_version,
|
||||||
|
cfg_name,
|
||||||
|
args.verbose,
|
||||||
|
jobs,
|
||||||
|
args.clean,
|
||||||
|
args.cleanup_artifacts,
|
||||||
|
)
|
||||||
|
if code != 0:
|
||||||
|
return code
|
||||||
|
|
||||||
|
print("[linux] Build completed")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
LIBRARY nsis7z
|
||||||
|
EXPORTS
|
||||||
|
Extract
|
||||||
|
ExtractWithDetails
|
||||||
|
ExtractWithCallback
|
||||||
|
ExtractWithFileCallback
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
PROG = nsis7z
|
||||||
|
DEF_FILE = Nsis7z.def
|
||||||
|
|
||||||
|
include ../Format7zF/Arc_gcc.mak
|
||||||
|
|
||||||
|
ifdef IS_MINGW
|
||||||
|
LOCAL_FLAGS_SYS = -DZ7_DEVICE_FILE
|
||||||
|
SYS_OBJS = \
|
||||||
|
$O/DLL.o \
|
||||||
|
$O/DllSecur.o \
|
||||||
|
$O/resource.o \
|
||||||
|
|
||||||
|
else
|
||||||
|
SYS_OBJS = \
|
||||||
|
$O/MyWindows.o \
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
LOCAL_FLAGS += \
|
||||||
|
$(LOCAL_FLAGS_SYS) \
|
||||||
|
$(LOCAL_FLAGS_EXTRA) \
|
||||||
|
|
||||||
|
NSIS_UI_OBJS = \
|
||||||
|
$O/NsisMain.o \
|
||||||
|
$O/NsisMainAr.o \
|
||||||
|
$O/NsisBreak.o \
|
||||||
|
$O/NsisExtractCallbackConsole.o \
|
||||||
|
$O/NsisUserInputUtils2.o \
|
||||||
|
$O/ConsoleOpenCallbackConsole.o \
|
||||||
|
$O/ConsolePercentPrinter.o \
|
||||||
|
$O/ConsoleClose.o \
|
||||||
|
$O/ConsoleUserInputUtils.o \
|
||||||
|
$O/Extract.o \
|
||||||
|
$O/HashCalc.o \
|
||||||
|
$O/OpenArchive.o \
|
||||||
|
$O/ArchiveExtractCallback.o \
|
||||||
|
$O/ArchiveOpenCallback.o \
|
||||||
|
$O/DefaultName.o \
|
||||||
|
$O/EnumDirItems.o \
|
||||||
|
$O/ExtractingFilePath.o \
|
||||||
|
$O/LoadCodecs.o \
|
||||||
|
$O/SetProperties.o \
|
||||||
|
$O/FileStreams.o \
|
||||||
|
$O/FileSystem.o \
|
||||||
|
$O/ErrorMsg.o \
|
||||||
|
$O/FileLink.o \
|
||||||
|
$O/FilePathAutoRename.o \
|
||||||
|
$O/SortUtils.o \
|
||||||
|
$O/PropIDUtils.o \
|
||||||
|
|
||||||
|
CURRENT_OBJS = \
|
||||||
|
$O/StdInStream.o \
|
||||||
|
$O/StdOutStream.o \
|
||||||
|
$O/StdAfx2.o \
|
||||||
|
$O/pluginapi.o \
|
||||||
|
$O/nsis7z.o \
|
||||||
|
|
||||||
|
OBJS = \
|
||||||
|
$(ARC_OBJS) \
|
||||||
|
$(SYS_OBJS) \
|
||||||
|
$(NSIS_UI_OBJS) \
|
||||||
|
$(CURRENT_OBJS) \
|
||||||
|
|
||||||
|
include ../../7zip_gcc.mak
|
||||||
|
|
||||||
|
$O/NsisMain.o: ../../UI/NSIS/Main.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/NsisMainAr.o: ../../UI/NSIS/MainAr.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/NsisBreak.o: ../../UI/NSIS/NSISBreak.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/NsisExtractCallbackConsole.o: ../../UI/NSIS/ExtractCallbackConsole.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/NsisUserInputUtils2.o: ../../UI/NSIS/UserInputUtils2.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/ConsoleOpenCallbackConsole.o: ../../UI/Console/OpenCallbackConsole.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/ConsolePercentPrinter.o: ../../UI/Console/PercentPrinter.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/ConsoleUserInputUtils.o: ../../UI/Console/UserInputUtils.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/StdAfx2.o: StdAfx2.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/pluginapi.o: pluginapi.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
|
|
||||||
|
$O/nsis7z.o: nsis7z.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $<
|
||||||
@@ -181,6 +181,7 @@ EXTRACTFUNCEND
|
|||||||
|
|
||||||
extern "C" BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
|
extern "C" BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
|
||||||
{
|
{
|
||||||
|
(void)lpReserved;
|
||||||
g_hInstance2=(HINSTANCE)hInst;
|
g_hInstance2=(HINSTANCE)hInst;
|
||||||
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
|
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ using namespace NDir;
|
|||||||
|
|
||||||
extern HWND g_hwndProgress;
|
extern HWND g_hwndProgress;
|
||||||
|
|
||||||
|
static const UInt64 k_SizeUnknown = (UInt64)(Int64)-1;
|
||||||
|
|
||||||
void CExtractCallbackConsole::UpdateProgress()
|
void CExtractCallbackConsole::UpdateProgress()
|
||||||
{
|
{
|
||||||
// Prima controlla se c'è il callback con filename (nuova funzione)
|
// Prima controlla se c'è il callback con filename (nuova funzione)
|
||||||
if (ProgressWithFileHandler != NULL)
|
if (ProgressWithFileHandler != NULL)
|
||||||
{
|
{
|
||||||
if (completedSize != -1 || totalSize > 0)
|
if (completedSize != k_SizeUnknown || totalSize > 0)
|
||||||
ProgressWithFileHandler(completedSize, totalSize, CurrentFileName.Ptr());
|
ProgressWithFileHandler(completedSize, totalSize, CurrentFileName.Ptr());
|
||||||
else
|
else
|
||||||
ProgressWithFileHandler(0, 0, CurrentFileName.Ptr());
|
ProgressWithFileHandler(0, 0, CurrentFileName.Ptr());
|
||||||
@@ -40,14 +42,14 @@ void CExtractCallbackConsole::UpdateProgress()
|
|||||||
// Altrimenti usa il callback originale (per compatibilitĂ )
|
// Altrimenti usa il callback originale (per compatibilitĂ )
|
||||||
else if (ProgressHandler != NULL)
|
else if (ProgressHandler != NULL)
|
||||||
{
|
{
|
||||||
if (completedSize != -1 || totalSize > 0)
|
if (completedSize != k_SizeUnknown || totalSize > 0)
|
||||||
ProgressHandler(completedSize, totalSize);
|
ProgressHandler(completedSize, totalSize);
|
||||||
else
|
else
|
||||||
ProgressHandler(0, 0);
|
ProgressHandler(0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 val)
|
Z7_COM7F_IMF(CExtractCallbackConsole::SetTotal(UInt64 val))
|
||||||
{
|
{
|
||||||
totalSize = val;
|
totalSize = val;
|
||||||
UpdateProgress();
|
UpdateProgress();
|
||||||
@@ -56,7 +58,7 @@ STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 val)
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *val)
|
Z7_COM7F_IMF(CExtractCallbackConsole::SetCompleted(const UInt64 *val))
|
||||||
{
|
{
|
||||||
completedSize = *val;
|
completedSize = *val;
|
||||||
UpdateProgress();
|
UpdateProgress();
|
||||||
@@ -65,31 +67,35 @@ STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *val)
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::AskOverwrite(
|
Z7_COM7F_IMF(CExtractCallbackConsole::AskOverwrite(
|
||||||
const wchar_t *existName, const FILETIME *, const UInt64 *,
|
const wchar_t *existName, const FILETIME *, const UInt64 *,
|
||||||
const wchar_t *newName, const FILETIME *, const UInt64 *,
|
const wchar_t *newName, const FILETIME *, const UInt64 *,
|
||||||
Int32 *answer)
|
Int32 *answer))
|
||||||
{
|
{
|
||||||
*answer = NOverwriteAnswer::kYesToAll;
|
(void)existName; (void)newName;
|
||||||
|
*answer = NOverwriteAnswer::kYesToAll;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 *position)
|
Z7_COM7F_IMF(CExtractCallbackConsole::PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 *position))
|
||||||
{
|
{
|
||||||
|
(void)isFolder; (void)askExtractMode; (void)position;
|
||||||
// Memorizza il nome del file corrente per passarlo al callback
|
// Memorizza il nome del file corrente per passarlo al callback
|
||||||
CurrentFileName = name;
|
CurrentFileName = name;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::MessageError(const wchar_t *message)
|
Z7_COM7F_IMF(CExtractCallbackConsole::MessageError(const wchar_t *message))
|
||||||
{
|
{
|
||||||
|
(void)message;
|
||||||
NumFileErrorsInCurrentArchive++;
|
NumFileErrorsInCurrentArchive++;
|
||||||
NumFileErrors++;
|
NumFileErrors++;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted)
|
Z7_COM7F_IMF(CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted))
|
||||||
{
|
{
|
||||||
|
(void)encrypted;
|
||||||
switch(opRes)
|
switch(opRes)
|
||||||
{
|
{
|
||||||
case NArchive::NExtract::NOperationResult::kOK:
|
case NArchive::NExtract::NOperationResult::kOK:
|
||||||
@@ -113,7 +119,7 @@ HRESULT CExtractCallbackConsole::SetPassword(const UString &password)
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
|
Z7_COM7F_IMF(CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password))
|
||||||
{
|
{
|
||||||
if (!PasswordIsDefined)
|
if (!PasswordIsDefined)
|
||||||
{
|
{
|
||||||
@@ -127,6 +133,7 @@ STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
|
|||||||
|
|
||||||
HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
|
HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
|
||||||
{
|
{
|
||||||
|
(void)name; (void)testMode;
|
||||||
NumArchives++;
|
NumArchives++;
|
||||||
NumFileErrorsInCurrentArchive = 0;
|
NumFileErrorsInCurrentArchive = 0;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
@@ -134,6 +141,7 @@ HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
|
|||||||
|
|
||||||
HRESULT CExtractCallbackConsole::OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result)
|
HRESULT CExtractCallbackConsole::OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result)
|
||||||
{
|
{
|
||||||
|
(void)codecs; (void)arcLink; (void)name;
|
||||||
if (result != S_OK)
|
if (result != S_OK)
|
||||||
{
|
{
|
||||||
NumArchiveErrors++;
|
NumArchiveErrors++;
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
#ifndef __STDAFX_H
|
#ifndef __STDAFX_H
|
||||||
#define __STDAFX_H
|
#define __STDAFX_H
|
||||||
|
|
||||||
#include <Windows.h>
|
#ifdef _WIN32
|
||||||
#include "7zip/Bundles/Nsis7z/pluginapi.h"
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#include "../../Bundles/Nsis7z/pluginapi.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Submodule
+1
Submodule versions/7-zip-zstd added at 6146959af0
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
|||||||
|
// StdAfx.h
|
||||||
|
|
||||||
|
#ifndef __STDAFX_H
|
||||||
|
#define __STDAFX_H
|
||||||
|
|
||||||
|
#include "Common/Common.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <commctrl.h>
|
||||||
|
#include "Common/MyWindows.h"
|
||||||
|
#include "Common/NewHandler.h"
|
||||||
|
#include "pluginapi.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// StdAfx.cpp
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include <winresrc.h>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* apih
|
||||||
|
*
|
||||||
|
* This file is a part of NSIS.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999-2013 Nullsoft and Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the zlib/libpng license (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Licence details can be found in the file COPYING.
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _NSIS_EXEHEAD_API_H_
|
||||||
|
#define _NSIS_EXEHEAD_API_H_
|
||||||
|
|
||||||
|
// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version
|
||||||
|
// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x))
|
||||||
|
// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {}
|
||||||
|
|
||||||
|
#define NSISPIAPIVER_1_0 0x00010000
|
||||||
|
#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0
|
||||||
|
|
||||||
|
// NSIS Plug-In Callback Messages
|
||||||
|
enum NSPIM
|
||||||
|
{
|
||||||
|
NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup
|
||||||
|
NSPIM_GUIUNLOAD, // Called after .onGUIEnd
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback()
|
||||||
|
// Return NULL for unknown messages
|
||||||
|
// Should always be __cdecl for future expansion possibilities
|
||||||
|
typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM);
|
||||||
|
|
||||||
|
// extra_parameters data structures containing other interesting stuff
|
||||||
|
// but the stack, variables and HWND passed on to plug-ins.
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int autoclose;
|
||||||
|
int all_user_var;
|
||||||
|
int exec_error;
|
||||||
|
int abort;
|
||||||
|
int exec_reboot; // NSIS_SUPPORT_REBOOT
|
||||||
|
int reboot_called; // NSIS_SUPPORT_REBOOT
|
||||||
|
int XXX_cur_insttype; // depreacted
|
||||||
|
int plugin_api_version; // see NSISPIAPIVER_CURR
|
||||||
|
// used to be XXX_insttype_changed
|
||||||
|
int silent; // NSIS_CONFIG_SILENT_SUPPORT
|
||||||
|
int instdir_error;
|
||||||
|
int rtl;
|
||||||
|
int errlvl;
|
||||||
|
int alter_reg_view;
|
||||||
|
int status_update;
|
||||||
|
} exec_flags_t;
|
||||||
|
|
||||||
|
#ifndef NSISCALL
|
||||||
|
# define NSISCALL __stdcall
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
exec_flags_t *exec_flags;
|
||||||
|
int (NSISCALL *ExecuteCodeSegment)(int, HWND);
|
||||||
|
void (NSISCALL *validate_filename)(TCHAR *);
|
||||||
|
int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors
|
||||||
|
} extra_parameters;
|
||||||
|
|
||||||
|
// Definitions for page showing plug-ins
|
||||||
|
// See Ui.c to understand better how they're used
|
||||||
|
|
||||||
|
// sent to the outer window to tell it to go to the next inner window
|
||||||
|
#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8)
|
||||||
|
|
||||||
|
// custom pages should send this message to let NSIS know they're ready
|
||||||
|
#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd)
|
||||||
|
|
||||||
|
// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning
|
||||||
|
#define NOTIFY_BYE_BYE 'x'
|
||||||
|
|
||||||
|
#endif /* _PLUGIN_H_ */
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#include "../../UI/NSIS/ExtractCallbackConsole.h"
|
||||||
|
#include "Common/StringConvert.h"
|
||||||
|
|
||||||
|
#pragma warning(disable: 4100)
|
||||||
|
|
||||||
|
#define IDC_PROGRESS 1004
|
||||||
|
#define IDC_INTROTEXT 1006
|
||||||
|
|
||||||
|
#define EXTRACTFUNC(funcname) extern "C" { \
|
||||||
|
void __declspec(dllexport) __cdecl funcname(HWND hwndParent, int string_size, \
|
||||||
|
TCHAR *variables, stack_t **stacktop, \
|
||||||
|
extra_parameters *extra) \
|
||||||
|
{ \
|
||||||
|
EXDLL_INIT();\
|
||||||
|
g_lastVal = -1; \
|
||||||
|
g_hwndParent=hwndParent; \
|
||||||
|
HWND hwndDlg = FindWindowEx(g_hwndParent, NULL, TEXT("#32770"), NULL); \
|
||||||
|
g_hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESS); \
|
||||||
|
g_hwndText = GetDlgItem(hwndDlg, IDC_INTROTEXT); \
|
||||||
|
TCHAR sArchive[1024], *outDir = getuservariable(INST_OUTDIR); \
|
||||||
|
popstring(sArchive); \
|
||||||
|
g_pluginExtra = extra; \
|
||||||
|
|
||||||
|
#define EXTRACTFUNCEND } }
|
||||||
|
|
||||||
|
HINSTANCE g_hInstance2;
|
||||||
|
HWND g_hwndParent;
|
||||||
|
HWND g_hwndProgress;
|
||||||
|
HWND g_hwndText;
|
||||||
|
extra_parameters *g_pluginExtra;
|
||||||
|
|
||||||
|
void DoInitialize();
|
||||||
|
int DoExtract(LPTSTR archive, LPTSTR dir, bool overwrite, bool expath, ExtractProgressHandler epc, const UStringVector& skipPatterns);
|
||||||
|
int DoExtractWithFile(LPTSTR archive, LPTSTR dir, bool overwrite, bool expath, ExtractProgressWithFileHandler epc, const UStringVector& skipPatterns);
|
||||||
|
|
||||||
|
int g_progressCallback = -1;
|
||||||
|
int g_lastVal = -1;
|
||||||
|
TCHAR* g_sDetails;
|
||||||
|
|
||||||
|
static void PopSkipPatterns(UStringVector& skipPatterns)
|
||||||
|
{
|
||||||
|
TCHAR *buf = new TCHAR[g_stringsize];
|
||||||
|
while (popstring(buf) == 0 && lstrlen(buf) > 0)
|
||||||
|
{
|
||||||
|
#ifdef UNICODE
|
||||||
|
skipPatterns.Add(UString(buf));
|
||||||
|
#else
|
||||||
|
skipPatterns.Add(MultiByteToUnicodeString(AString(buf)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetPercentComplete(UInt64 completedSize, UInt64 totalSize)
|
||||||
|
{
|
||||||
|
if (totalSize == 0) return 0;
|
||||||
|
const int nsisProgressMax = 30000;
|
||||||
|
int val = (int)((completedSize*nsisProgressMax)/totalSize);
|
||||||
|
if (val < 0) return 0;
|
||||||
|
if (val > nsisProgressMax) return nsisProgressMax;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleProgressHandler(UInt64 completedSize, UInt64 totalSize)
|
||||||
|
{
|
||||||
|
int val = GetPercentComplete(completedSize, totalSize);
|
||||||
|
if (g_lastVal != val)
|
||||||
|
SendMessage(g_hwndProgress, PBM_SETPOS, g_lastVal = val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackProgressHandler(UInt64 completedSize, UInt64 totalSize)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
|
||||||
|
if (totalSize > 0)
|
||||||
|
{
|
||||||
|
val = GetPercentComplete(completedSize, totalSize);
|
||||||
|
|
||||||
|
static TCHAR buf[32];
|
||||||
|
wsprintf(buf, TEXT("%lu"), totalSize);
|
||||||
|
pushstring(buf);
|
||||||
|
wsprintf(buf, TEXT("%lu"), completedSize);
|
||||||
|
pushstring(buf);
|
||||||
|
g_pluginExtra->ExecuteCodeSegment(g_progressCallback-1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_lastVal != val)
|
||||||
|
SendMessage(g_hwndProgress, PBM_SETPOS, g_lastVal = val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackFileProgressHandler(UInt64 completedSize, UInt64 totalSize, const wchar_t *fileName)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
|
||||||
|
if (totalSize > 0)
|
||||||
|
val = GetPercentComplete(completedSize, totalSize);
|
||||||
|
|
||||||
|
// Notify NSIS only when a new file starts (filename changes)
|
||||||
|
if (fileName && fileName[0])
|
||||||
|
{
|
||||||
|
static TCHAR fileNameBuf[MAX_PATH];
|
||||||
|
static TCHAR prevFileNameBuf[MAX_PATH];
|
||||||
|
static TCHAR buf[32];
|
||||||
|
|
||||||
|
lstrcpyn(fileNameBuf, (const TCHAR*)fileName, MAX_PATH);
|
||||||
|
if (lstrcmp(fileNameBuf, prevFileNameBuf) != 0)
|
||||||
|
{
|
||||||
|
lstrcpy(prevFileNameBuf, fileNameBuf);
|
||||||
|
pushstring(fileNameBuf);
|
||||||
|
wsprintf(buf, TEXT("%lu"), (DWORD)totalSize);
|
||||||
|
pushstring(buf);
|
||||||
|
wsprintf(buf, TEXT("%lu"), (DWORD)completedSize);
|
||||||
|
pushstring(buf);
|
||||||
|
g_pluginExtra->ExecuteCodeSegment(g_progressCallback-1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_lastVal != val)
|
||||||
|
SendMessage(g_hwndProgress, PBM_SETPOS, g_lastVal = val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetailsProgressHandler(UInt64 completedSize, UInt64 totalSize)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
|
||||||
|
if (totalSize > 0)
|
||||||
|
{
|
||||||
|
val = GetPercentComplete(completedSize, totalSize);
|
||||||
|
|
||||||
|
TCHAR* buf = new TCHAR[g_stringsize];
|
||||||
|
TCHAR* buf2 = new TCHAR[g_stringsize];
|
||||||
|
wsprintf(buf, TEXT("%d%% (%d / %d MB)"), (int)(val?val/300:0), (int)(completedSize?completedSize/(1024*1024):0), (int)(totalSize/(1024*1024)));
|
||||||
|
wsprintf(buf2, g_sDetails, buf);
|
||||||
|
SetWindowText(g_hwndText, buf2);
|
||||||
|
delete[] buf;
|
||||||
|
delete[] buf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_lastVal != val)
|
||||||
|
SendMessage(g_hwndProgress, PBM_SETPOS, g_lastVal = val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTRACTFUNC(Extract)
|
||||||
|
{
|
||||||
|
UStringVector skipPatterns;
|
||||||
|
PopSkipPatterns(skipPatterns);
|
||||||
|
DoExtract(sArchive, outDir, true, true, (ExtractProgressHandler)SimpleProgressHandler, skipPatterns);
|
||||||
|
}
|
||||||
|
EXTRACTFUNCEND
|
||||||
|
|
||||||
|
EXTRACTFUNC(ExtractWithDetails)
|
||||||
|
{
|
||||||
|
g_sDetails = new TCHAR[string_size];
|
||||||
|
popstring(g_sDetails);
|
||||||
|
UStringVector skipPatterns;
|
||||||
|
PopSkipPatterns(skipPatterns);
|
||||||
|
DoExtract(sArchive, outDir, true, true, (ExtractProgressHandler)DetailsProgressHandler, skipPatterns);
|
||||||
|
delete[] g_sDetails;
|
||||||
|
}
|
||||||
|
EXTRACTFUNCEND
|
||||||
|
|
||||||
|
EXTRACTFUNC(ExtractWithCallback)
|
||||||
|
{
|
||||||
|
g_progressCallback = popint();
|
||||||
|
UStringVector skipPatterns;
|
||||||
|
PopSkipPatterns(skipPatterns);
|
||||||
|
DoExtract(sArchive, outDir, true, true, (ExtractProgressHandler)CallbackProgressHandler, skipPatterns);
|
||||||
|
}
|
||||||
|
EXTRACTFUNCEND
|
||||||
|
|
||||||
|
EXTRACTFUNC(ExtractWithFileCallback)
|
||||||
|
{
|
||||||
|
g_progressCallback = popint();
|
||||||
|
UStringVector skipPatterns;
|
||||||
|
PopSkipPatterns(skipPatterns);
|
||||||
|
DoExtractWithFile(sArchive, outDir, true, true, CallbackFileProgressHandler, skipPatterns);
|
||||||
|
}
|
||||||
|
EXTRACTFUNCEND
|
||||||
|
|
||||||
|
extern "C" BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
|
||||||
|
{
|
||||||
|
g_hInstance2=(HINSTANCE)hInst;
|
||||||
|
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
|
||||||
|
{
|
||||||
|
DoInitialize();
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* nsis_tchar.h
|
||||||
|
*
|
||||||
|
* This file is a part of NSIS.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999-2013 Nullsoft and Contributors
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty.
|
||||||
|
*
|
||||||
|
* For Unicode support by Jim Park -- 08/30/2007
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Jim Park: Only those we use are listed here.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
|
||||||
|
#ifndef _T
|
||||||
|
#define __T(x) L ## x
|
||||||
|
#define _T(x) __T(x)
|
||||||
|
#define _TEXT(x) __T(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _TCHAR_DEFINED
|
||||||
|
#define _TCHAR_DEFINED
|
||||||
|
#if !defined(_NATIVE_WCHAR_T_DEFINED) && !defined(_WCHAR_T_DEFINED)
|
||||||
|
typedef unsigned short TCHAR;
|
||||||
|
#else
|
||||||
|
typedef wchar_t TCHAR;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// program
|
||||||
|
#define _tenviron _wenviron
|
||||||
|
#define __targv __wargv
|
||||||
|
|
||||||
|
// printfs
|
||||||
|
#define _ftprintf fwprintf
|
||||||
|
#define _sntprintf _snwprintf
|
||||||
|
#if (defined(_MSC_VER) && (_MSC_VER<=1310)) || defined(__MINGW32__)
|
||||||
|
# define _stprintf swprintf
|
||||||
|
#else
|
||||||
|
# define _stprintf _swprintf
|
||||||
|
#endif
|
||||||
|
#define _tprintf wprintf
|
||||||
|
#define _vftprintf vfwprintf
|
||||||
|
#define _vsntprintf _vsnwprintf
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER<=1310)
|
||||||
|
# define _vstprintf vswprintf
|
||||||
|
#else
|
||||||
|
# define _vstprintf _vswprintf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// scanfs
|
||||||
|
#define _tscanf wscanf
|
||||||
|
#define _stscanf swscanf
|
||||||
|
|
||||||
|
// string manipulations
|
||||||
|
#define _tcscat wcscat
|
||||||
|
#define _tcschr wcschr
|
||||||
|
#define _tcsclen wcslen
|
||||||
|
#define _tcscpy wcscpy
|
||||||
|
#define _tcsdup _wcsdup
|
||||||
|
#define _tcslen wcslen
|
||||||
|
#define _tcsnccpy wcsncpy
|
||||||
|
#define _tcsncpy wcsncpy
|
||||||
|
#define _tcsrchr wcsrchr
|
||||||
|
#define _tcsstr wcsstr
|
||||||
|
#define _tcstok wcstok
|
||||||
|
|
||||||
|
// string comparisons
|
||||||
|
#define _tcscmp wcscmp
|
||||||
|
#define _tcsicmp _wcsicmp
|
||||||
|
#define _tcsncicmp _wcsnicmp
|
||||||
|
#define _tcsncmp wcsncmp
|
||||||
|
#define _tcsnicmp _wcsnicmp
|
||||||
|
|
||||||
|
// upper / lower
|
||||||
|
#define _tcslwr _wcslwr
|
||||||
|
#define _tcsupr _wcsupr
|
||||||
|
#define _totlower towlower
|
||||||
|
#define _totupper towupper
|
||||||
|
|
||||||
|
// conversions to numbers
|
||||||
|
#define _tcstoi64 _wcstoi64
|
||||||
|
#define _tcstol wcstol
|
||||||
|
#define _tcstoul wcstoul
|
||||||
|
#define _tstof _wtof
|
||||||
|
#define _tstoi _wtoi
|
||||||
|
#define _tstoi64 _wtoi64
|
||||||
|
#define _ttoi _wtoi
|
||||||
|
#define _ttoi64 _wtoi64
|
||||||
|
#define _ttol _wtol
|
||||||
|
|
||||||
|
// conversion from numbers to strings
|
||||||
|
#define _itot _itow
|
||||||
|
#define _ltot _ltow
|
||||||
|
#define _i64tot _i64tow
|
||||||
|
#define _ui64tot _ui64tow
|
||||||
|
|
||||||
|
// file manipulations
|
||||||
|
#define _tfopen _wfopen
|
||||||
|
#define _topen _wopen
|
||||||
|
#define _tremove _wremove
|
||||||
|
#define _tunlink _wunlink
|
||||||
|
|
||||||
|
// reading and writing to i/o
|
||||||
|
#define _fgettc fgetwc
|
||||||
|
#define _fgetts fgetws
|
||||||
|
#define _fputts fputws
|
||||||
|
#define _gettchar getwchar
|
||||||
|
|
||||||
|
// directory
|
||||||
|
#define _tchdir _wchdir
|
||||||
|
|
||||||
|
// environment
|
||||||
|
#define _tgetenv _wgetenv
|
||||||
|
#define _tsystem _wsystem
|
||||||
|
|
||||||
|
// time
|
||||||
|
#define _tcsftime wcsftime
|
||||||
|
|
||||||
|
#else // ANSI
|
||||||
|
|
||||||
|
#ifndef _T
|
||||||
|
#define _T(x) x
|
||||||
|
#define _TEXT(x) x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _TCHAR_DEFINED
|
||||||
|
#define _TCHAR_DEFINED
|
||||||
|
typedef char TCHAR;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// program
|
||||||
|
#define _tenviron environ
|
||||||
|
#define __targv __argv
|
||||||
|
|
||||||
|
// printfs
|
||||||
|
#define _ftprintf fprintf
|
||||||
|
#define _sntprintf _snprintf
|
||||||
|
#define _stprintf sprintf
|
||||||
|
#define _tprintf printf
|
||||||
|
#define _vftprintf vfprintf
|
||||||
|
#define _vsntprintf _vsnprintf
|
||||||
|
#define _vstprintf vsprintf
|
||||||
|
|
||||||
|
// scanfs
|
||||||
|
#define _tscanf scanf
|
||||||
|
#define _stscanf sscanf
|
||||||
|
|
||||||
|
// string manipulations
|
||||||
|
#define _tcscat strcat
|
||||||
|
#define _tcschr strchr
|
||||||
|
#define _tcsclen strlen
|
||||||
|
#define _tcscnlen strnlen
|
||||||
|
#define _tcscpy strcpy
|
||||||
|
#define _tcsdup _strdup
|
||||||
|
#define _tcslen strlen
|
||||||
|
#define _tcsnccpy strncpy
|
||||||
|
#define _tcsrchr strrchr
|
||||||
|
#define _tcsstr strstr
|
||||||
|
#define _tcstok strtok
|
||||||
|
|
||||||
|
// string comparisons
|
||||||
|
#define _tcscmp strcmp
|
||||||
|
#define _tcsicmp _stricmp
|
||||||
|
#define _tcsncmp strncmp
|
||||||
|
#define _tcsncicmp _strnicmp
|
||||||
|
#define _tcsnicmp _strnicmp
|
||||||
|
|
||||||
|
// upper / lower
|
||||||
|
#define _tcslwr _strlwr
|
||||||
|
#define _tcsupr _strupr
|
||||||
|
|
||||||
|
#define _totupper toupper
|
||||||
|
#define _totlower tolower
|
||||||
|
|
||||||
|
// conversions to numbers
|
||||||
|
#define _tcstol strtol
|
||||||
|
#define _tcstoul strtoul
|
||||||
|
#define _tstof atof
|
||||||
|
#define _tstoi atoi
|
||||||
|
#define _tstoi64 _atoi64
|
||||||
|
#define _tstoi64 _atoi64
|
||||||
|
#define _ttoi atoi
|
||||||
|
#define _ttoi64 _atoi64
|
||||||
|
#define _ttol atol
|
||||||
|
|
||||||
|
// conversion from numbers to strings
|
||||||
|
#define _i64tot _i64toa
|
||||||
|
#define _itot _itoa
|
||||||
|
#define _ltot _ltoa
|
||||||
|
#define _ui64tot _ui64toa
|
||||||
|
|
||||||
|
// file manipulations
|
||||||
|
#define _tfopen fopen
|
||||||
|
#define _topen _open
|
||||||
|
#define _tremove remove
|
||||||
|
#define _tunlink _unlink
|
||||||
|
|
||||||
|
// reading and writing to i/o
|
||||||
|
#define _fgettc fgetc
|
||||||
|
#define _fgetts fgets
|
||||||
|
#define _fputts fputs
|
||||||
|
#define _gettchar getchar
|
||||||
|
|
||||||
|
// directory
|
||||||
|
#define _tchdir _chdir
|
||||||
|
|
||||||
|
// environment
|
||||||
|
#define _tgetenv getenv
|
||||||
|
#define _tsystem system
|
||||||
|
|
||||||
|
// time
|
||||||
|
#define _tcsftime strftime
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// is functions (the same in Unicode / ANSI)
|
||||||
|
#define _istgraph isgraph
|
||||||
|
#define _istascii __isascii
|
||||||
|
|
||||||
|
#define __TFILE__ _T(__FILE__)
|
||||||
|
#define __TDATE__ _T(__DATE__)
|
||||||
|
#define __TTIME__ _T(__TIME__)
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#ifdef _countof
|
||||||
|
#define COUNTOF _countof
|
||||||
|
#else
|
||||||
|
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned int g_stringsize;
|
||||||
|
stack_t **g_stacktop;
|
||||||
|
TCHAR *g_variables;
|
||||||
|
|
||||||
|
// utility functions (not required but often useful)
|
||||||
|
|
||||||
|
int NSISCALL popstring(TCHAR *str)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
th=(*g_stacktop);
|
||||||
|
if (str) lstrcpy(str,th->text);
|
||||||
|
*g_stacktop = th->next;
|
||||||
|
GlobalFree((HGLOBAL)th);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL popstringn(TCHAR *str, int maxlen)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
th=(*g_stacktop);
|
||||||
|
if (str) lstrcpyn(str,th->text,maxlen?maxlen:g_stringsize);
|
||||||
|
*g_stacktop = th->next;
|
||||||
|
GlobalFree((HGLOBAL)th);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL pushstring(const TCHAR *str)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop) return;
|
||||||
|
th=(stack_t*)GlobalAlloc(GPTR,(sizeof(stack_t)+(g_stringsize)*sizeof(TCHAR)));
|
||||||
|
lstrcpyn(th->text,str,g_stringsize);
|
||||||
|
th->next=*g_stacktop;
|
||||||
|
*g_stacktop=th;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCHAR* NSISCALL getuservariable(const int varnum)
|
||||||
|
{
|
||||||
|
if (varnum < 0 || varnum >= __INST_LAST) return NULL;
|
||||||
|
return g_variables+varnum*g_stringsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL setuservariable(const int varnum, const TCHAR *var)
|
||||||
|
{
|
||||||
|
if (var != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
lstrcpy(g_variables + varnum*g_stringsize, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
int NSISCALL PopStringA(char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
||||||
|
int rval = popstring(wideStr);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL PopStringNA(char* ansiStr, int maxlen)
|
||||||
|
{
|
||||||
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, realLen*sizeof(wchar_t));
|
||||||
|
int rval = popstringn(wideStr, realLen);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL PushStringA(const char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
pushstring(wideStr);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
lstrcpyW(wideStr, getuservariable(varnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = getuservariable(varnum);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr)
|
||||||
|
{
|
||||||
|
if (ansiStr != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = g_variables + varnum * g_stringsize;
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ANSI defs
|
||||||
|
int NSISCALL PopStringW(wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
||||||
|
int rval = popstring(ansiStr);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL PopStringNW(wchar_t* wideStr, int maxlen)
|
||||||
|
{
|
||||||
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, realLen);
|
||||||
|
int rval = popstringn(ansiStr, realLen);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL PushStringW(wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
pushstring(ansiStr);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = getuservariable(varnum);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
||||||
|
{
|
||||||
|
lstrcpyA(ansiStr, getuservariable(varnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
if (wideStr != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
{
|
||||||
|
char* ansiStr = g_variables + varnum * g_stringsize;
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// playing with integers
|
||||||
|
|
||||||
|
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s)
|
||||||
|
{
|
||||||
|
INT_PTR v=0;
|
||||||
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10;
|
||||||
|
else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10;
|
||||||
|
else break;
|
||||||
|
v<<=4;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0'))
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('7')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v<<=3;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int sign=0;
|
||||||
|
if (*s == _T('-')) sign++; else s--;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s) - _T('0');
|
||||||
|
if (c < 0 || c > 9) break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
if (sign) v = -v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NSISCALL myatou(const TCHAR *s)
|
||||||
|
{
|
||||||
|
unsigned int v=0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
unsigned int c=*s++;
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL myatoi_or(const TCHAR *s)
|
||||||
|
{
|
||||||
|
int v=0;
|
||||||
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10;
|
||||||
|
else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10;
|
||||||
|
else break;
|
||||||
|
v<<=4;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0'))
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('7')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v<<=3;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int sign=0;
|
||||||
|
if (*s == _T('-')) sign++; else s--;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s) - _T('0');
|
||||||
|
if (c < 0 || c > 9) break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
if (sign) v = -v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for simple ORed expressions
|
||||||
|
if (*s == _T('|'))
|
||||||
|
{
|
||||||
|
v |= myatoi_or(s+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
INT_PTR NSISCALL popintptr()
|
||||||
|
{
|
||||||
|
TCHAR buf[128];
|
||||||
|
if (popstringn(buf,COUNTOF(buf)))
|
||||||
|
return 0;
|
||||||
|
return nsishelper_str_to_ptr(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL popint_or()
|
||||||
|
{
|
||||||
|
TCHAR buf[128];
|
||||||
|
if (popstringn(buf,COUNTOF(buf)))
|
||||||
|
return 0;
|
||||||
|
return myatoi_or(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL pushintptr(INT_PTR value)
|
||||||
|
{
|
||||||
|
TCHAR buffer[30];
|
||||||
|
wsprintf(buffer, sizeof(void*) > 4 ? _T("%Id") : _T("%d"), value);
|
||||||
|
pushstring(buffer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#ifndef ___NSIS_PLUGIN__H___
|
||||||
|
#define ___NSIS_PLUGIN__H___
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "api.h"
|
||||||
|
#ifdef _UNICODE
|
||||||
|
#ifndef _T
|
||||||
|
#define __T(x) L ## x
|
||||||
|
#define _T(x) __T(x)
|
||||||
|
#define _TEXT(x) __T(x)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifndef _T
|
||||||
|
#define _T(x) x
|
||||||
|
#define _TEXT(x) x
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NSISCALL
|
||||||
|
# define NSISCALL __stdcall
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXDLL_INIT() { \
|
||||||
|
g_stringsize=string_size; \
|
||||||
|
g_stacktop=stacktop; \
|
||||||
|
g_variables=variables; }
|
||||||
|
|
||||||
|
typedef struct _stack_t {
|
||||||
|
struct _stack_t *next;
|
||||||
|
TCHAR text[1]; // this should be the length of string_size
|
||||||
|
} stack_t;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
INST_0, // $0
|
||||||
|
INST_1, // $1
|
||||||
|
INST_2, // $2
|
||||||
|
INST_3, // $3
|
||||||
|
INST_4, // $4
|
||||||
|
INST_5, // $5
|
||||||
|
INST_6, // $6
|
||||||
|
INST_7, // $7
|
||||||
|
INST_8, // $8
|
||||||
|
INST_9, // $9
|
||||||
|
INST_R0, // $R0
|
||||||
|
INST_R1, // $R1
|
||||||
|
INST_R2, // $R2
|
||||||
|
INST_R3, // $R3
|
||||||
|
INST_R4, // $R4
|
||||||
|
INST_R5, // $R5
|
||||||
|
INST_R6, // $R6
|
||||||
|
INST_R7, // $R7
|
||||||
|
INST_R8, // $R8
|
||||||
|
INST_R9, // $R9
|
||||||
|
INST_CMDLINE, // $CMDLINE
|
||||||
|
INST_INSTDIR, // $INSTDIR
|
||||||
|
INST_OUTDIR, // $OUTDIR
|
||||||
|
INST_EXEDIR, // $EXEDIR
|
||||||
|
INST_LANG, // $LANGUAGE
|
||||||
|
__INST_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
extern unsigned int g_stringsize;
|
||||||
|
extern stack_t **g_stacktop;
|
||||||
|
extern TCHAR *g_variables;
|
||||||
|
|
||||||
|
void NSISCALL pushstring(const TCHAR *str);
|
||||||
|
void NSISCALL pushintptr(INT_PTR value);
|
||||||
|
#define pushint(v) pushintptr((INT_PTR)(v))
|
||||||
|
int NSISCALL popstring(TCHAR *str); // 0 on success, 1 on empty stack
|
||||||
|
int NSISCALL popstringn(TCHAR *str, int maxlen); // with length limit, pass 0 for g_stringsize
|
||||||
|
INT_PTR NSISCALL popintptr();
|
||||||
|
#define popint() ( (int) popintptr() )
|
||||||
|
int NSISCALL popint_or(); // with support for or'ing (2|4|8)
|
||||||
|
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s);
|
||||||
|
#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer
|
||||||
|
unsigned int NSISCALL myatou(const TCHAR *s); // converts a string to an unsigned integer, decimal only
|
||||||
|
int NSISCALL myatoi_or(const TCHAR *s); // with support for or'ing (2|4|8)
|
||||||
|
TCHAR* NSISCALL getuservariable(const int varnum);
|
||||||
|
void NSISCALL setuservariable(const int varnum, const TCHAR *var);
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
#define PopStringW(x) popstring(x)
|
||||||
|
#define PushStringW(x) pushstring(x)
|
||||||
|
#define SetUserVariableW(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
|
int NSISCALL PopStringA(char* ansiStr);
|
||||||
|
void NSISCALL PushStringA(const char* ansiStr);
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
||||||
|
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr);
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ANSI defs
|
||||||
|
|
||||||
|
#define PopStringA(x) popstring(x)
|
||||||
|
#define PushStringA(x) pushstring(x)
|
||||||
|
#define SetUserVariableA(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
|
int NSISCALL PopStringW(wchar_t* wideStr);
|
||||||
|
void NSISCALL PushStringW(wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
||||||
|
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif//!___NSIS_PLUGIN__H___
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//{{NO_DEPENDENCIES}}
|
||||||
|
// Microsoft Visual C++ generated include file.
|
||||||
|
// Used by resource.rc
|
||||||
|
//
|
||||||
|
#define MY_VER_BUILD 0
|
||||||
|
#define DBG_FL 0
|
||||||
|
#define MY_VER_MAJOR 9
|
||||||
|
#define MY_VER_MINOR 20
|
||||||
|
|
||||||
|
// Next default values for new objects
|
||||||
|
//
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
#define _APS_NEXT_RESOURCE_VALUE 101
|
||||||
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
|
#define _APS_NEXT_CONTROL_VALUE 1000
|
||||||
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Microsoft Visual C++ generated resource script.
|
||||||
|
//
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
|
#define APSTUDIO_READONLY_SYMBOLS
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Generated from the TEXTINCLUDE 2 resource.
|
||||||
|
//
|
||||||
|
#include "afxres.h"
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
#undef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// English (United States) resources
|
||||||
|
|
||||||
|
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||||
|
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
|
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// TEXTINCLUDE
|
||||||
|
//
|
||||||
|
|
||||||
|
1 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"resource.h\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
2 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"#include ""afxres.h""\r\n"
|
||||||
|
"\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
3 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"\r\n"
|
||||||
|
"\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Version
|
||||||
|
//
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 25,01,0,0
|
||||||
|
PRODUCTVERSION 25,01,0,0
|
||||||
|
FILEFLAGSMASK 0x3fL
|
||||||
|
#ifdef _DEBUG
|
||||||
|
FILEFLAGS 0x1L
|
||||||
|
#else
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
#endif
|
||||||
|
FILEOS 0x40004L
|
||||||
|
FILETYPE 0x2L
|
||||||
|
FILESUBTYPE 0x0L
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "CompanyName", "Igor Pavlov"
|
||||||
|
VALUE "FileDescription", "7-Zip NSIS Plug-in"
|
||||||
|
VALUE "FileVersion", "25.01.0.0"
|
||||||
|
VALUE "InternalName", "nsis7z"
|
||||||
|
VALUE "LegalCopyright", "Copyright (c) 1999-2025 Igor Pavlov, Nik Medved, Marek Mizanin, Stuart Welch"
|
||||||
|
VALUE "OriginalFilename", "nsis7z.dll"
|
||||||
|
VALUE "ProductName", "7-Zip"
|
||||||
|
VALUE "ProductVersion", "25.01.0.0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
|
|
||||||
|
#endif // English (United States) resources
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef APSTUDIO_INVOKED
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Generated from the TEXTINCLUDE 3 resource.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
#endif // not APSTUDIO_INVOKED
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// ExtractCallbackConsole.h
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#include "Common/Common.h"
|
||||||
|
#include "ExtractCallbackConsole.h"
|
||||||
|
#include "UserInputUtils2.h"
|
||||||
|
#include "NSISBreak.h"
|
||||||
|
|
||||||
|
#include "Common/Wildcard.h"
|
||||||
|
|
||||||
|
#include "Windows/FileDir.h"
|
||||||
|
#include "Windows/FileFind.h"
|
||||||
|
#include "Windows/TimeUtils.h"
|
||||||
|
#include "Windows/Defs.h"
|
||||||
|
#include "Windows/PropVariant.h"
|
||||||
|
#include "Windows/ErrorMsg.h"
|
||||||
|
#include "Windows/PropVariantConv.h"
|
||||||
|
|
||||||
|
#include "7zip/Common/FilePathAutoRename.h"
|
||||||
|
|
||||||
|
#include "7zip/UI/Common/ExtractingFilePath.h"
|
||||||
|
|
||||||
|
using namespace NWindows;
|
||||||
|
using namespace NFile;
|
||||||
|
using namespace NDir;
|
||||||
|
|
||||||
|
extern HWND g_hwndProgress;
|
||||||
|
|
||||||
|
void CExtractCallbackConsole::UpdateProgress()
|
||||||
|
{
|
||||||
|
// Prima controlla se c'è il callback con filename (nuova funzione)
|
||||||
|
if (ProgressWithFileHandler != NULL)
|
||||||
|
{
|
||||||
|
if (completedSize != -1 || totalSize > 0)
|
||||||
|
ProgressWithFileHandler(completedSize, totalSize, CurrentFileName.Ptr());
|
||||||
|
else
|
||||||
|
ProgressWithFileHandler(0, 0, CurrentFileName.Ptr());
|
||||||
|
}
|
||||||
|
// Altrimenti usa il callback originale (per compatibilitĂ )
|
||||||
|
else if (ProgressHandler != NULL)
|
||||||
|
{
|
||||||
|
if (completedSize != -1 || totalSize > 0)
|
||||||
|
ProgressHandler(completedSize, totalSize);
|
||||||
|
else
|
||||||
|
ProgressHandler(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 val)
|
||||||
|
{
|
||||||
|
totalSize = val;
|
||||||
|
UpdateProgress();
|
||||||
|
if (NNSISBreak::TestBreakSignal())
|
||||||
|
return E_ABORT;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *val)
|
||||||
|
{
|
||||||
|
completedSize = *val;
|
||||||
|
UpdateProgress();
|
||||||
|
if (NNSISBreak::TestBreakSignal())
|
||||||
|
return E_ABORT;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::AskOverwrite(
|
||||||
|
const wchar_t *existName, const FILETIME *, const UInt64 *,
|
||||||
|
const wchar_t *newName, const FILETIME *, const UInt64 *,
|
||||||
|
Int32 *answer)
|
||||||
|
{
|
||||||
|
*answer = NOverwriteAnswer::kYesToAll;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 *position)
|
||||||
|
{
|
||||||
|
// Memorizza il nome del file corrente per passarlo al callback
|
||||||
|
CurrentFileName = name;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::MessageError(const wchar_t *message)
|
||||||
|
{
|
||||||
|
NumFileErrorsInCurrentArchive++;
|
||||||
|
NumFileErrors++;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted)
|
||||||
|
{
|
||||||
|
switch(opRes)
|
||||||
|
{
|
||||||
|
case NArchive::NExtract::NOperationResult::kOK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
NumFileErrorsInCurrentArchive++;
|
||||||
|
NumFileErrors++;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef Z7_NO_CRYPTO
|
||||||
|
|
||||||
|
HRESULT CExtractCallbackConsole::SetPassword(const UString &password)
|
||||||
|
{
|
||||||
|
PasswordIsDefined = true;
|
||||||
|
Password = password;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
|
||||||
|
{
|
||||||
|
if (!PasswordIsDefined)
|
||||||
|
{
|
||||||
|
Password = GetPassword(OutStream);
|
||||||
|
PasswordIsDefined = true;
|
||||||
|
}
|
||||||
|
return StringToBstr(Password, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
|
||||||
|
{
|
||||||
|
NumArchives++;
|
||||||
|
NumFileErrorsInCurrentArchive = 0;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CExtractCallbackConsole::OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result)
|
||||||
|
{
|
||||||
|
if (result != S_OK)
|
||||||
|
{
|
||||||
|
NumArchiveErrors++;
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CExtractCallbackConsole::ThereAreNoFiles()
|
||||||
|
{
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CExtractCallbackConsole::ExtractResult(HRESULT result)
|
||||||
|
{
|
||||||
|
if (result == S_OK)
|
||||||
|
{
|
||||||
|
if (NumFileErrorsInCurrentArchive != 0)
|
||||||
|
{
|
||||||
|
NumArchiveErrors++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
NumArchiveErrors++;
|
||||||
|
if (result == E_ABORT || result == ERROR_DISK_FULL)
|
||||||
|
return result;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// ExtractCallbackConsole.h
|
||||||
|
// NSIS version adapted for 7-Zip 25.01 API
|
||||||
|
|
||||||
|
#ifndef __EXTRACTCALLBACKCONSOLE_H
|
||||||
|
#define __EXTRACTCALLBACKCONSOLE_H
|
||||||
|
|
||||||
|
#include "Common/MyCom.h"
|
||||||
|
#include "Common/MyString.h"
|
||||||
|
#include "Common/StdOutStream.h"
|
||||||
|
#include "7zip/Common/FileStreams.h"
|
||||||
|
#include "7zip/IPassword.h"
|
||||||
|
#include "7zip/Archive/IArchive.h"
|
||||||
|
#include "7zip/UI/Common/ArchiveExtractCallback.h"
|
||||||
|
#include "7zip/UI/Console/OpenCallbackConsole.h"
|
||||||
|
|
||||||
|
typedef void (*ExtractProgressHandler)(UInt64 completedSize, UInt64 totalSize);
|
||||||
|
typedef void (*ExtractProgressWithFileHandler)(UInt64 completedSize, UInt64 totalSize, const wchar_t *fileName);
|
||||||
|
|
||||||
|
class CExtractCallbackConsole Z7_final:
|
||||||
|
public IFolderArchiveExtractCallback,
|
||||||
|
public IExtractCallbackUI,
|
||||||
|
#ifndef Z7_NO_CRYPTO
|
||||||
|
public ICryptoGetTextPassword,
|
||||||
|
#endif
|
||||||
|
public COpenCallbackConsole,
|
||||||
|
public CMyUnknownImp
|
||||||
|
{
|
||||||
|
Z7_COM_QI_BEGIN2(IFolderArchiveExtractCallback)
|
||||||
|
#ifndef Z7_NO_CRYPTO
|
||||||
|
Z7_COM_QI_ENTRY(ICryptoGetTextPassword)
|
||||||
|
#endif
|
||||||
|
Z7_COM_QI_END
|
||||||
|
Z7_COM_ADDREF_RELEASE
|
||||||
|
|
||||||
|
// IProgress
|
||||||
|
Z7_IFACE_COM7_IMP(IProgress)
|
||||||
|
|
||||||
|
// IFolderArchiveExtractCallback
|
||||||
|
Z7_IFACE_COM7_IMP(IFolderArchiveExtractCallback)
|
||||||
|
|
||||||
|
// IExtractCallbackUI (non-COM interface)
|
||||||
|
Z7_IFACE_IMP(IExtractCallbackUI)
|
||||||
|
|
||||||
|
#ifndef Z7_NO_CRYPTO
|
||||||
|
Z7_IFACE_COM7_IMP(ICryptoGetTextPassword)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
#ifndef Z7_NO_CRYPTO
|
||||||
|
bool PasswordIsDefined;
|
||||||
|
UString Password;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UInt64 NumArchives;
|
||||||
|
UInt64 NumArchiveErrors;
|
||||||
|
UInt64 NumFileErrors;
|
||||||
|
UInt64 NumFileErrorsInCurrentArchive;
|
||||||
|
|
||||||
|
CStdOutStream *OutStream;
|
||||||
|
|
||||||
|
UInt64 totalSize, completedSize, lastVal;
|
||||||
|
|
||||||
|
ExtractProgressHandler ProgressHandler;
|
||||||
|
ExtractProgressWithFileHandler ProgressWithFileHandler;
|
||||||
|
UString CurrentFileName;
|
||||||
|
|
||||||
|
CExtractCallbackConsole():
|
||||||
|
PasswordIsDefined(false),
|
||||||
|
NumArchives(0),
|
||||||
|
NumArchiveErrors(0),
|
||||||
|
NumFileErrors(0),
|
||||||
|
NumFileErrorsInCurrentArchive(0),
|
||||||
|
OutStream(NULL),
|
||||||
|
totalSize((UInt64)(Int64)-1),
|
||||||
|
completedSize(0),
|
||||||
|
lastVal(0),
|
||||||
|
ProgressHandler(NULL),
|
||||||
|
ProgressWithFileHandler(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
NumArchives = 0;
|
||||||
|
NumArchiveErrors = 0;
|
||||||
|
NumFileErrors = 0;
|
||||||
|
NumFileErrorsInCurrentArchive = 0;
|
||||||
|
|
||||||
|
totalSize = (UInt64)(Int64)-1;
|
||||||
|
completedSize = 0;
|
||||||
|
lastVal = 0;
|
||||||
|
ProgressHandler = NULL;
|
||||||
|
ProgressWithFileHandler = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
// Main.cpp
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#include "Common/Common.h"
|
||||||
|
#include "Common/MyInitGuid.h"
|
||||||
|
|
||||||
|
#include "Common/CommandLineParser.h"
|
||||||
|
#include "Common/MyException.h"
|
||||||
|
#include "Common/IntToString.h"
|
||||||
|
#include "Common/StdOutStream.h"
|
||||||
|
#include "Common/StringConvert.h"
|
||||||
|
#include "Common/StringToInt.h"
|
||||||
|
|
||||||
|
#include "Windows/FileDir.h"
|
||||||
|
#include "Windows/FileName.h"
|
||||||
|
#include "Windows/Defs.h"
|
||||||
|
#include "Windows/ErrorMsg.h"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "Windows/MemoryLock.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "7zip/IPassword.h"
|
||||||
|
#include "7zip/ICoder.h"
|
||||||
|
#include "7zip/UI/Common/UpdateAction.h"
|
||||||
|
#include "7zip/UI/Common/Update.h"
|
||||||
|
#include "7zip/UI/Common/Extract.h"
|
||||||
|
#include "7zip/UI/Common/ArchiveCommandLine.h"
|
||||||
|
#include "7zip/UI/Common/ExitCode.h"
|
||||||
|
#ifdef EXTERNAL_CODECS
|
||||||
|
#include "7zip/UI/Common/LoadCodecs.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//#include "../../Compress/LZMA_Alone/LzmaBenchCon.h"
|
||||||
|
|
||||||
|
#include "7zip/UI/Console/OpenCallbackConsole.h"
|
||||||
|
#include "ExtractCallbackConsole.h"
|
||||||
|
|
||||||
|
#include "7zip/MyVersion.h"
|
||||||
|
|
||||||
|
#if defined( _WIN32) && defined( _7ZIP_LARGE_PAGES)
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include "C/Alloc.h"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace NWindows;
|
||||||
|
using namespace NFile;
|
||||||
|
using namespace NCommandLineParser;
|
||||||
|
|
||||||
|
HINSTANCE g_hInstance = 0;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// exception messages
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static void GetArguments(int numArguments, const char *arguments[], UStringVector &parts)
|
||||||
|
{
|
||||||
|
parts.Clear();
|
||||||
|
for(int i = 0; i < numArguments; i++)
|
||||||
|
{
|
||||||
|
UString s = MultiByteToUnicodeString(arguments[i]);
|
||||||
|
parts.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXTERNAL_CODECS
|
||||||
|
static void PrintString(CStdOutStream &stdStream, const AString &s, int size)
|
||||||
|
{
|
||||||
|
int len = s.Length();
|
||||||
|
stdStream << s;
|
||||||
|
for (int i = len; i < size; i++)
|
||||||
|
stdStream << ' ';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline char GetHex(Byte value)
|
||||||
|
{
|
||||||
|
return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
void SwitchFileAPIEncoding(BOOL ansi)
|
||||||
|
{
|
||||||
|
if (ansi)
|
||||||
|
{
|
||||||
|
SetFileApisToOEM();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetFileApisToANSI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int DoExtractArchive(UString archive, UString targetDir, bool overwrite, bool extractPaths, ExtractProgressHandler epc, const UStringVector& skipPatterns)
|
||||||
|
{
|
||||||
|
CCodecs *codecs = new CCodecs;
|
||||||
|
CMyComPtr<IUnknown> compressCodecsInfo = codecs;
|
||||||
|
HRESULT result = codecs->Load();
|
||||||
|
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
if (codecs->Formats.Size() == 0) throw -1;
|
||||||
|
|
||||||
|
CIntVector formatIndices;
|
||||||
|
|
||||||
|
if (!codecs->FindFormatForArchiveType(L"7z", formatIndices))
|
||||||
|
{
|
||||||
|
throw -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL bApisAreAnsi = AreFileApisANSI();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (bApisAreAnsi)
|
||||||
|
SwitchFileAPIEncoding(bApisAreAnsi);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CExtractCallbackConsole *ecs = new CExtractCallbackConsole();
|
||||||
|
CMyComPtr<IFolderArchiveExtractCallback> extractCallback = ecs;
|
||||||
|
ecs->Init();
|
||||||
|
ecs->ProgressHandler = epc; // Imposta DOPO Init()
|
||||||
|
|
||||||
|
COpenCallbackConsole openCallback;
|
||||||
|
|
||||||
|
CExtractOptions eo;
|
||||||
|
eo.StdOutMode = false;
|
||||||
|
eo.PathMode = extractPaths?NExtract::NPathMode::kCurPaths:NExtract::NPathMode::kNoPaths;
|
||||||
|
eo.TestMode = false;
|
||||||
|
eo.OverwriteMode = overwrite?NExtract::NOverwriteMode::kOverwrite:NExtract::NOverwriteMode::kSkip;
|
||||||
|
eo.OutputDir = targetDir;
|
||||||
|
eo.YesToAll = true;
|
||||||
|
#ifdef COMPRESS_MT
|
||||||
|
CObjectVector<CProperty> prp;
|
||||||
|
eo.Properties = prp;
|
||||||
|
#endif
|
||||||
|
UString errorMessage;
|
||||||
|
CDecompressStat stat;
|
||||||
|
NWildcard::CCensor wildcardCensor;
|
||||||
|
NWildcard::CCensorPathProps props;
|
||||||
|
props.Recursive = true;
|
||||||
|
props.WildcardMatching = true;
|
||||||
|
wildcardCensor.AddItem(NWildcard::ECensorPathMode::k_FullPath, true, L"*", props);
|
||||||
|
for (unsigned i = 0; i < skipPatterns.Size(); i++)
|
||||||
|
{
|
||||||
|
const UString &p = skipPatterns[i];
|
||||||
|
if (p.IsEmpty()) continue;
|
||||||
|
// Keep only single-name patterns: multi-segment ones land in a separate Pair
|
||||||
|
// and would be silently ignored by Pairs.Front().Head below.
|
||||||
|
if (p.Find(L'\\') >= 0 || p.Find(L'/') >= 0) continue;
|
||||||
|
wildcardCensor.AddItem(NWildcard::ECensorPathMode::k_FullPath, false, p, props);
|
||||||
|
}
|
||||||
|
UStringVector ArchivePathsSorted;
|
||||||
|
UStringVector ArchivePathsFullSorted;
|
||||||
|
ArchivePathsSorted.Add(archive);
|
||||||
|
UString fullPath;
|
||||||
|
NFile::NDir::MyGetFullPathName(archive, fullPath);
|
||||||
|
ArchivePathsFullSorted.Add(fullPath);
|
||||||
|
|
||||||
|
UStringVector v1, v2;
|
||||||
|
v1.Add(fs2us(archive));
|
||||||
|
v2.Add(fs2us(archive));
|
||||||
|
const NWildcard::CCensorNode &wildcardCensorHead =
|
||||||
|
wildcardCensor.Pairs.Front().Head;
|
||||||
|
|
||||||
|
result = Extract(
|
||||||
|
codecs, CObjectVector<COpenType>(), CIntVector(),
|
||||||
|
v1, v2,
|
||||||
|
wildcardCensorHead,
|
||||||
|
eo, ecs, ecs, ecs,
|
||||||
|
#ifndef Z7_SFX
|
||||||
|
NULL, // hash
|
||||||
|
#endif
|
||||||
|
errorMessage, stat);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (bApisAreAnsi)
|
||||||
|
SwitchFileAPIEncoding(!bApisAreAnsi);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
if (!errorMessage.IsEmpty())
|
||||||
|
{
|
||||||
|
if (result == S_OK)
|
||||||
|
result = E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ecs->NumArchiveErrors != 0 || ecs->NumFileErrors != 0)
|
||||||
|
{
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
return NExitCode::kFatalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DoExtractArchiveWithFile(UString archive, UString targetDir, bool overwrite, bool extractPaths, ExtractProgressWithFileHandler epc, const UStringVector& skipPatterns)
|
||||||
|
{
|
||||||
|
CCodecs *codecs = new CCodecs;
|
||||||
|
CMyComPtr<IUnknown> compressCodecsInfo = codecs;
|
||||||
|
HRESULT result = codecs->Load();
|
||||||
|
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
if (codecs->Formats.Size() == 0) throw -1;
|
||||||
|
|
||||||
|
CIntVector formatIndices;
|
||||||
|
|
||||||
|
if (!codecs->FindFormatForArchiveType(L"7z", formatIndices))
|
||||||
|
{
|
||||||
|
throw -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL bApisAreAnsi = AreFileApisANSI();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (bApisAreAnsi)
|
||||||
|
SwitchFileAPIEncoding(bApisAreAnsi);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CExtractCallbackConsole *ecs = new CExtractCallbackConsole();
|
||||||
|
CMyComPtr<IFolderArchiveExtractCallback> extractCallback = ecs;
|
||||||
|
ecs->Init();
|
||||||
|
ecs->ProgressWithFileHandler = epc; // Imposta DOPO Init() per evitare che venga resettato
|
||||||
|
|
||||||
|
COpenCallbackConsole openCallback;
|
||||||
|
|
||||||
|
CExtractOptions eo;
|
||||||
|
eo.StdOutMode = false;
|
||||||
|
eo.PathMode = extractPaths?NExtract::NPathMode::kCurPaths:NExtract::NPathMode::kNoPaths;
|
||||||
|
eo.TestMode = false;
|
||||||
|
eo.OverwriteMode = overwrite?NExtract::NOverwriteMode::kOverwrite:NExtract::NOverwriteMode::kSkip;
|
||||||
|
eo.OutputDir = targetDir;
|
||||||
|
eo.YesToAll = true;
|
||||||
|
#ifdef COMPRESS_MT
|
||||||
|
CObjectVector<CProperty> prp;
|
||||||
|
eo.Properties = prp;
|
||||||
|
#endif
|
||||||
|
UString errorMessage;
|
||||||
|
CDecompressStat stat;
|
||||||
|
NWildcard::CCensor wildcardCensor;
|
||||||
|
NWildcard::CCensorPathProps props;
|
||||||
|
props.Recursive = true;
|
||||||
|
props.WildcardMatching = true;
|
||||||
|
wildcardCensor.AddItem(NWildcard::ECensorPathMode::k_FullPath, true, L"*", props);
|
||||||
|
for (unsigned i = 0; i < skipPatterns.Size(); i++)
|
||||||
|
{
|
||||||
|
const UString &p = skipPatterns[i];
|
||||||
|
if (p.IsEmpty()) continue;
|
||||||
|
// Keep only single-name patterns: multi-segment ones land in a separate Pair
|
||||||
|
// and would be silently ignored by Pairs.Front().Head below.
|
||||||
|
if (p.Find(L'\\') >= 0 || p.Find(L'/') >= 0) continue;
|
||||||
|
wildcardCensor.AddItem(NWildcard::ECensorPathMode::k_FullPath, false, p, props);
|
||||||
|
}
|
||||||
|
UStringVector ArchivePathsSorted;
|
||||||
|
UStringVector ArchivePathsFullSorted;
|
||||||
|
ArchivePathsSorted.Add(archive);
|
||||||
|
UString fullPath;
|
||||||
|
NFile::NDir::MyGetFullPathName(archive, fullPath);
|
||||||
|
ArchivePathsFullSorted.Add(fullPath);
|
||||||
|
|
||||||
|
UStringVector v1, v2;
|
||||||
|
v1.Add(fs2us(archive));
|
||||||
|
v2.Add(fs2us(archive));
|
||||||
|
const NWildcard::CCensorNode &wildcardCensorHead =
|
||||||
|
wildcardCensor.Pairs.Front().Head;
|
||||||
|
|
||||||
|
result = Extract(
|
||||||
|
codecs, CObjectVector<COpenType>(), CIntVector(),
|
||||||
|
v1, v2,
|
||||||
|
wildcardCensorHead,
|
||||||
|
eo, ecs, ecs, ecs,
|
||||||
|
#ifndef Z7_SFX
|
||||||
|
NULL, // hash
|
||||||
|
#endif
|
||||||
|
errorMessage, stat);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (bApisAreAnsi)
|
||||||
|
SwitchFileAPIEncoding(!bApisAreAnsi);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
if (!errorMessage.IsEmpty())
|
||||||
|
{
|
||||||
|
if (result == S_OK)
|
||||||
|
result = E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ecs->NumArchiveErrors != 0 || ecs->NumFileErrors != 0)
|
||||||
|
{
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
return NExitCode::kFatalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != S_OK)
|
||||||
|
throw CSystemException(result);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// MainAr.cpp
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
// #include <locale.h>
|
||||||
|
|
||||||
|
#include "Common/Common.h"
|
||||||
|
|
||||||
|
#include "Windows/ErrorMsg.h"
|
||||||
|
|
||||||
|
#include "Common/StdOutStream.h"
|
||||||
|
#include "Common/NewHandler.h"
|
||||||
|
#include "Common/MyException.h"
|
||||||
|
#include "Common/StringConvert.h"
|
||||||
|
|
||||||
|
#include "7zip/UI/Common/ExitCode.h"
|
||||||
|
#include "7zip/UI/Common/ArchiveCommandLine.h"
|
||||||
|
#include "ExtractCallbackConsole.h"
|
||||||
|
#include "NSISBreak.h"
|
||||||
|
|
||||||
|
using namespace NWindows;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#pragma warning(disable : 4996)
|
||||||
|
|
||||||
|
#ifndef _UNICODE
|
||||||
|
bool g_IsNT = false;
|
||||||
|
#endif
|
||||||
|
#if !defined(_UNICODE) || !defined(_WIN64)
|
||||||
|
static inline bool IsItWindowsNT()
|
||||||
|
{
|
||||||
|
OSVERSIONINFO versionInfo;
|
||||||
|
versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
|
||||||
|
if (!::GetVersionEx(&versionInfo))
|
||||||
|
return false;
|
||||||
|
return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void DoInitialize()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
#ifndef _WIN64
|
||||||
|
if (!IsItWindowsNT())
|
||||||
|
{
|
||||||
|
//(*g_StdStream) << "This program requires Windows NT/2000/2003/2008/XP/Vista";
|
||||||
|
// return NExitCode::kFatalError;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
g_IsNT = IsItWindowsNT();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern int DoExtractArchive(UString archive, UString targetDir, bool overwrite, bool extractPathes, ExtractProgressHandler epc, const UStringVector& skipPatterns);
|
||||||
|
extern int DoExtractArchiveWithFile(UString archive, UString targetDir, bool overwrite, bool extractPathes, ExtractProgressWithFileHandler epc, const UStringVector& skipPatterns);
|
||||||
|
|
||||||
|
int DoExtract(LPTSTR archive, LPTSTR dir, bool overwrite, bool expath, ExtractProgressHandler epc, const UStringVector& skipPatterns)
|
||||||
|
{
|
||||||
|
if (!archive || !dir) return -1;
|
||||||
|
int res = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#ifdef UNICODE
|
||||||
|
UString uarchive(archive);
|
||||||
|
UString udir(dir);
|
||||||
|
#else
|
||||||
|
UString uarchive = MultiByteToUnicodeString(AString(archive));
|
||||||
|
UString udir = MultiByteToUnicodeString(AString(dir));
|
||||||
|
#endif
|
||||||
|
res = DoExtractArchive(uarchive, udir, overwrite, expath, epc, skipPatterns);
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DoExtractWithFile(LPTSTR archive, LPTSTR dir, bool overwrite, bool expath, ExtractProgressWithFileHandler epc, const UStringVector& skipPatterns)
|
||||||
|
{
|
||||||
|
if (!archive || !dir) return -1;
|
||||||
|
int res = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#ifdef UNICODE
|
||||||
|
UString uarchive(archive);
|
||||||
|
UString udir(dir);
|
||||||
|
#else
|
||||||
|
UString uarchive = MultiByteToUnicodeString(AString(archive));
|
||||||
|
UString udir = MultiByteToUnicodeString(AString(dir));
|
||||||
|
#endif
|
||||||
|
res = DoExtractArchiveWithFile(uarchive, udir, overwrite, expath, epc, skipPatterns);
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// ConsoleClose.cpp
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#include "NSISBreak.h"
|
||||||
|
|
||||||
|
static int g_BreakCounter = 0;
|
||||||
|
|
||||||
|
namespace NNSISBreak {
|
||||||
|
|
||||||
|
void SendBreakSignal()
|
||||||
|
{
|
||||||
|
g_BreakCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestBreakSignal()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (g_BreakCounter > 0)
|
||||||
|
return true;
|
||||||
|
*/
|
||||||
|
return (g_BreakCounter > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckCtrlBreak()
|
||||||
|
{
|
||||||
|
if (TestBreakSignal())
|
||||||
|
throw CCtrlBreakException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef __NSISBREAKUTILS_H
|
||||||
|
#define __NSISBREAKUTILS_H
|
||||||
|
|
||||||
|
namespace NNSISBreak {
|
||||||
|
|
||||||
|
bool TestBreakSignal();
|
||||||
|
void SendBreakSignal();
|
||||||
|
|
||||||
|
class CCtrlBreakException
|
||||||
|
{};
|
||||||
|
|
||||||
|
void CheckCtrlBreak();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// StdAfx.h
|
||||||
|
|
||||||
|
#ifndef __STDAFX_H
|
||||||
|
#define __STDAFX_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include "7zip/Bundles/Nsis7z/pluginapi.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// UserInputUtils.cpp
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
|
||||||
|
#include "Common/StdInStream.h"
|
||||||
|
#include "Common/StringConvert.h"
|
||||||
|
|
||||||
|
#include "UserInputUtils2.h"
|
||||||
|
|
||||||
|
static const char kYes = 'Y';
|
||||||
|
static const char kNo = 'N';
|
||||||
|
static const char kYesAll = 'A';
|
||||||
|
static const char kNoAll = 'S';
|
||||||
|
static const char kAutoRename = 'U';
|
||||||
|
static const char kQuit = 'Q';
|
||||||
|
|
||||||
|
static const char *kFirstQuestionMessage = "?\n";
|
||||||
|
static const char *kHelpQuestionMessage =
|
||||||
|
"(Y)es / (N)o / (A)lways / (S)kip all / A(u)to rename / (Q)uit? ";
|
||||||
|
|
||||||
|
// return true if pressed Quite;
|
||||||
|
// in: anAll
|
||||||
|
// out: anAll, anYes;
|
||||||
|
|
||||||
|
NUserAnswerMode::EEnum ScanUserYesNoAllQuit2(CStdOutStream *outStream)
|
||||||
|
{
|
||||||
|
(*outStream) << kFirstQuestionMessage;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
(*outStream) << kHelpQuestionMessage;
|
||||||
|
AString scannedString;
|
||||||
|
g_StdIn.ScanAStringUntilNewLine(scannedString);
|
||||||
|
scannedString.Trim();
|
||||||
|
if(!scannedString.IsEmpty())
|
||||||
|
switch(::MyCharUpper(scannedString[0]))
|
||||||
|
{
|
||||||
|
case kYes:
|
||||||
|
return NUserAnswerMode::kYes;
|
||||||
|
case kNo:
|
||||||
|
return NUserAnswerMode::kNo;
|
||||||
|
case kYesAll:
|
||||||
|
return NUserAnswerMode::kYesAll;
|
||||||
|
case kNoAll:
|
||||||
|
return NUserAnswerMode::kNoAll;
|
||||||
|
case kAutoRename:
|
||||||
|
return NUserAnswerMode::kAutoRename;
|
||||||
|
case kQuit:
|
||||||
|
return NUserAnswerMode::kQuit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UString GetPassword(CStdOutStream *outStream)
|
||||||
|
{
|
||||||
|
(*outStream) << "\nEnter password:";
|
||||||
|
outStream->Flush();
|
||||||
|
AString oemPassword;
|
||||||
|
g_StdIn.ScanAStringUntilNewLine(oemPassword);
|
||||||
|
return MultiByteToUnicodeString(oemPassword, CP_OEMCP);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// UserInputUtils.h
|
||||||
|
|
||||||
|
#ifndef __USERINPUTUTILS_H
|
||||||
|
#define __USERINPUTUTILS_H
|
||||||
|
|
||||||
|
#include "Common/StdOutStream.h"
|
||||||
|
|
||||||
|
namespace NUserAnswerMode {
|
||||||
|
|
||||||
|
enum EEnum
|
||||||
|
{
|
||||||
|
kYes,
|
||||||
|
kNo,
|
||||||
|
kYesAll,
|
||||||
|
kNoAll,
|
||||||
|
kAutoRename,
|
||||||
|
kQuit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NUserAnswerMode::EEnum ScanUserYesNoAllQuit2(CStdOutStream *outStream);
|
||||||
|
UString GetPassword(CStdOutStream *outStream);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user