Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0faf0fb360 | |||
| 9be53c7e22 | |||
| d80b025219 | |||
| 91fe17cda2 | |||
| 4b12e21dd4 | |||
| e402720a12 | |||
| 5e738d749b | |||
| 5cfade0df1 | |||
| ce9e40d2b6 | |||
| 2b01cf9e75 | |||
| 505d5cf158 | |||
| 3298293474 | |||
| 52513bb1c9 | |||
| 940a8d6c7c |
@@ -52,7 +52,16 @@ jobs:
|
||||
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
- name: Build ${{ matrix.config }}
|
||||
run: python build_plugin.py --config ${{ matrix.config }} --verbose
|
||||
run: python build_plugin.py --configs ${{ matrix.config }} --verbosity normal
|
||||
|
||||
- name: Collect DLL
|
||||
shell: pwsh
|
||||
run: |
|
||||
$map = @{ 'x86-ansi' = 'x86-ansi'; 'x86-unicode' = 'x86-unicode'; 'x64-unicode' = 'amd64-unicode' }
|
||||
$src = "plugins\$($map['${{ matrix.config }}'])\nsis7z.dll"
|
||||
$dst = "dist\${{ matrix.config }}"
|
||||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||||
Copy-Item $src $dst
|
||||
|
||||
- name: Upload DLL
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -61,3 +70,58 @@ jobs:
|
||||
path: dist/**
|
||||
if-no-files-found: error
|
||||
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 }}
|
||||
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:
|
||||
needs: build
|
||||
needs: [build, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -60,7 +60,6 @@ desktop.ini
|
||||
# Migration/automation scripts (temporary, contain tokens — never commit)
|
||||
tools/t*.py
|
||||
tools/check_*.py
|
||||
tools/legacy/
|
||||
|
||||
# Plugin-specific
|
||||
versions/*/CPP/7zip/*/Release/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "versions/7-zip-zstd"]
|
||||
path = versions/7-zip-zstd
|
||||
url = https://github.com/mcmilk/7-Zip-zstd.git
|
||||
+24
-1
@@ -7,6 +7,27 @@ e il progetto aderisce al [Semantic Versioning](https://semver.org/spec/v2.0.0.h
|
||||
|
||||
## [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
|
||||
|
||||
### Fixed
|
||||
- Restored `tools/legacy/` build scripts lost from repository
|
||||
- Fixed `UnicodeEncodeError` on CI: replaced Unicode checkmarks with ASCII OK/FAIL in build summary
|
||||
- Fixed MSB8020 toolset error on GitHub Actions: `build_plugin_2600_vs2026.py` now detects the installed VS toolset dynamically (falls back to v143 on VS2022 runners)
|
||||
- Build script for 26.00 selects `Nsis7z_vs2026.vcxproj` (v145) or `Nsis7z.vcxproj` (v143) based on detected toolset
|
||||
|
||||
## [2.0.0] — 2026-04-29
|
||||
|
||||
### Added
|
||||
@@ -15,5 +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)
|
||||
- Documentazione completa (README, CONTRIBUTING, SECURITY)
|
||||
|
||||
[Unreleased]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/compare/v2.0.0...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.0]: https://gitea.emulab.it/Simone/nsis-plugin-ns7zip/releases/tag/v2.0.0
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# Nsis7z NSIS Plugin
|
||||
|
||||
**Plugin NSIS per l'estrazione di archivi 7z, ZIP e NSIS**
|
||||
NSIS plugin for extracting 7z, ZIP and NSIS archives.
|
||||
|
||||
---
|
||||
|
||||
## Versioni Disponibili
|
||||
## Available Versions
|
||||
|
||||
| Versione | 7-Zip | Formati | Note |
|
||||
| --------------------------------- | ----------- | ---------------------------------- | ----------------------------- |
|
||||
| [19.00](7zip-19.00/README.md) | 7-Zip 19.00 | 7z, LZMA, XZ, Split | Versione originale aggiornata |
|
||||
| [25.01](7zip-25.01/README.md) | 7-Zip 25.01 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | Con supporto NSIS archive |
|
||||
| [**26.00**](7zip-26.00/README.md) | 7-Zip 26.00 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | **Consigliata** |
|
||||
| Version | 7-Zip | Formats | Notes |
|
||||
|---------|-------|---------|-------|
|
||||
| [19.00](7zip-19.00/README.md) | 7-Zip 19.00 | 7z, LZMA, XZ, Split | Original updated version |
|
||||
| [25.01](7zip-25.01/README.md) | 7-Zip 25.01 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | With NSIS archive support |
|
||||
| [**26.00**](7zip-26.00/README.md) | 7-Zip 26.00 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | **Recommended** |
|
||||
|
||||
## Architetture Supportate
|
||||
## Supported Architectures
|
||||
|
||||
| Architettura | Descrizione |
|
||||
| ----------------- | -------------------- |
|
||||
| Architecture | Description |
|
||||
|--------------|-------------|
|
||||
| x86-ansi | 32-bit ANSI (legacy) |
|
||||
| x86-unicode | 32-bit Unicode |
|
||||
| **amd64-unicode** | 64-bit Unicode |
|
||||
@@ -26,13 +26,13 @@
|
||||
!addplugindir "plugins\x86-unicode"
|
||||
|
||||
Section
|
||||
; Estrazione semplice (7z, ZIP, NSIS)
|
||||
; Simple extraction (7z, ZIP, NSIS)
|
||||
Nsis7z::Extract "$EXEDIR\data.7z"
|
||||
|
||||
; Con testo di progresso nella label
|
||||
; With progress text in label
|
||||
Nsis7z::ExtractWithDetails "$EXEDIR\data.7z" "Installing %s..."
|
||||
|
||||
; Con callback per mostrare i file nella listbox
|
||||
; With callback to show files in listbox
|
||||
GetFunctionAddress $0 MyExtractCallback
|
||||
Nsis7z::ExtractWithFileCallback "$EXEDIR\data.7z" $0
|
||||
SectionEnd
|
||||
@@ -47,66 +47,68 @@ FunctionEnd
|
||||
|
||||
## Build Scripts
|
||||
|
||||
| Script | Versione | Toolset |
|
||||
| ----------------------------- | -------- | ------------- |
|
||||
| `build_plugin_vs2022.py` | 19.00 | VS2022 (v143) |
|
||||
| `build_plugin_vs2026.py` | 19.00 | VS2026 (v145) |
|
||||
| `build_plugin_2501_vs2022.py` | 25.01 | VS2022 (v143) |
|
||||
| `build_plugin_2501_vs2026.py` | 25.01 | VS2026 (v145) |
|
||||
| `build_plugin_2600_vs2026.py` | 26.00 | VS2026 (v145) |
|
||||
The repo includes a single unified `build_plugin.py` script that replaces the previous per-version scripts.
|
||||
|
||||
### Compilazione
|
||||
### Build
|
||||
|
||||
```powershell
|
||||
cd ns7zip
|
||||
# Recommended: 7-Zip 26.00 with auto-detected toolset
|
||||
python build_plugin.py
|
||||
|
||||
# Versione 26.00 (consigliata)
|
||||
python build_plugin_2600_vs2026.py
|
||||
# Select 7-Zip version (19.00 | 25.01 | 26.00)
|
||||
python build_plugin.py --7zip-version 25.01
|
||||
|
||||
# Versione 25.01
|
||||
python build_plugin_2501_vs2026.py
|
||||
# Specific toolset (2022|2026|auto)
|
||||
python build_plugin.py --toolset 2022
|
||||
|
||||
# Versione 19.00
|
||||
python build_plugin_vs2026.py
|
||||
# Linux host path (cross-build with MinGW-w64, 26.00)
|
||||
python build_plugin.py --host linux
|
||||
|
||||
# Print version and exit
|
||||
python build_plugin.py --version
|
||||
```
|
||||
|
||||
## Struttura Repository
|
||||
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
|
||||
|
||||
```
|
||||
ns7zip/
|
||||
├── build_plugin_vs2022.py # Build 19.00 VS2022
|
||||
├── build_plugin_vs2026.py # Build 19.00 VS2026
|
||||
├── build_plugin_2501_vs2022.py # Build 25.01 VS2022
|
||||
├── build_plugin_2501_vs2026.py # Build 25.01 VS2026
|
||||
├── build_plugin_2600_vs2026.py # Build 26.00 VS2026
|
||||
├── rebuild_nsis7z1900-src.ps1 # Ricostruisce sorgenti 19.00
|
||||
├── 7zip-19.00/ # 7-Zip 19.00 modificato
|
||||
├── 7zip-25.01/ # 7-Zip 25.01 modificato (ZIP + NSIS handler)
|
||||
└── 7zip-26.00/ # 7-Zip 26.00 modificato (ZIP + NSIS handler)
|
||||
├── build_plugin.py # Unified build script (all versions)
|
||||
├── rebuild_nsis7z1900-src.ps1 # Rebuilds 19.00 sources
|
||||
├── 7zip-19.00/ # 7-Zip 19.00 modified
|
||||
├── 7zip-25.01/ # 7-Zip 25.01 modified (ZIP + NSIS handler)
|
||||
└── 7zip-26.00/ # 7-Zip 26.00 modified (ZIP + NSIS handler)
|
||||
```
|
||||
|
||||
## Modifiche rispetto all'originale
|
||||
## Changes from Original
|
||||
|
||||
### NSIS Archive Handler (25.01, 26.00)
|
||||
Aggiunto supporto per estrarre archivi `.exe` creati con NSIS (non presente nel plugin originale):
|
||||
Added support for extracting `.exe` archives created with NSIS (not present in the original plugin):
|
||||
- `Archive\Nsis\NsisHandler.cpp/h`
|
||||
- `Archive\Nsis\NsisIn.cpp/h`
|
||||
- `Archive\Nsis\NsisDecode.cpp/h`
|
||||
- `Archive\Nsis\NsisRegister.cpp`
|
||||
- `Compress\BZip2Decoder.cpp/h` (richiesto da NsisDecode)
|
||||
- `Compress\BZip2Decoder.cpp/h` (required by NsisDecode)
|
||||
- `Compress\BZip2Crc.cpp`
|
||||
|
||||
### Bug Fix (25.01, 26.00)
|
||||
- **Divide-by-zero**: aggiunto guard `if (totalSize == 0) return 0` in `GetPercentComplete`
|
||||
- **ExtractWithFileCallback**: callback NSIS triggerata solo al cambio filename (evita crash con `totalSize = UINT64_MAX` alla prima chiamata `SetTotal`)
|
||||
### Bug Fixes (25.01, 26.00)
|
||||
- **Divide-by-zero**: added guard `if (totalSize == 0) return 0` in `GetPercentComplete`
|
||||
- **ExtractWithFileCallback**: NSIS callback triggered only on filename change (avoids crash with `totalSize = UINT64_MAX` on first `SetTotal` call)
|
||||
|
||||
## License
|
||||
|
||||
- **7-Zip**: LGPL 2.1 + BSD 3-clause
|
||||
- **Plugin NSIS**: Afrow UK
|
||||
- **NSIS Plugin**: Afrow UK
|
||||
|
||||
## Credits
|
||||
|
||||
- Igor Pavlov (7-Zip)
|
||||
- Afrow UK (Plugin originale)
|
||||
- Simone (Supporto x64, VS2022/VS2026, ZIP, NSIS handler, ExtractWithFileCallback)
|
||||
- Afrow UK (Original plugin)
|
||||
- Simone (x64 support, VS2022/VS2026, ZIP, NSIS handler, ExtractWithFileCallback)
|
||||
|
||||
---
|
||||
|
||||
*See [README_IT.md](README_IT.md) for the Italian version.*
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Nsis7z NSIS Plugin
|
||||
|
||||
**Plugin NSIS per l'estrazione di archivi 7z, ZIP e NSIS**
|
||||
|
||||
---
|
||||
|
||||
## Versioni Disponibili
|
||||
|
||||
| Versione | 7-Zip | Formati | Note |
|
||||
| --------------------------------- | ----------- | ---------------------------------- | ----------------------------- |
|
||||
| [19.00](7zip-19.00/README.md) | 7-Zip 19.00 | 7z, LZMA, XZ, Split | Versione originale aggiornata |
|
||||
| [25.01](7zip-25.01/README.md) | 7-Zip 25.01 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | Con supporto NSIS archive |
|
||||
| [**26.00**](7zip-26.00/README.md) | 7-Zip 26.00 | 7z, ZIP, LZMA, XZ, Split, **NSIS** | **Consigliata** |
|
||||
|
||||
## Architetture Supportate
|
||||
|
||||
| Architettura | Descrizione |
|
||||
| ----------------- | -------------------- |
|
||||
| x86-ansi | 32-bit ANSI (legacy) |
|
||||
| x86-unicode | 32-bit Unicode |
|
||||
| **amd64-unicode** | 64-bit Unicode |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```nsis
|
||||
!addplugindir "plugins\x86-unicode"
|
||||
|
||||
Section
|
||||
; Estrazione semplice (7z, ZIP, NSIS)
|
||||
Nsis7z::Extract "$EXEDIR\data.7z"
|
||||
|
||||
; Con testo di progresso nella label
|
||||
Nsis7z::ExtractWithDetails "$EXEDIR\data.7z" "Installing %s..."
|
||||
|
||||
; Con callback per mostrare i file nella listbox
|
||||
GetFunctionAddress $0 MyExtractCallback
|
||||
Nsis7z::ExtractWithFileCallback "$EXEDIR\data.7z" $0
|
||||
SectionEnd
|
||||
|
||||
Function MyExtractCallback
|
||||
Pop $0 ; completedSize
|
||||
Pop $1 ; totalSize
|
||||
Pop $2 ; fileName
|
||||
DetailPrint "$2"
|
||||
FunctionEnd
|
||||
```
|
||||
|
||||
## Build Scripts
|
||||
|
||||
Il repo include un unico script unificato `build_plugin.py` che sostituisce i precedenti script per-versione.
|
||||
|
||||
### Compilazione
|
||||
|
||||
```powershell
|
||||
# Raccomandato: 7-Zip 26.00 con toolset rilevato automaticamente
|
||||
python build_plugin.py
|
||||
|
||||
# Seleziona versione 7-Zip (19.00 | 25.01 | 26.00)
|
||||
python build_plugin.py --7zip-version 25.01
|
||||
|
||||
# Toolset specifico (2022|2026|auto)
|
||||
python build_plugin.py --toolset 2022
|
||||
|
||||
# Stampa versione ed esce
|
||||
python build_plugin.py --version
|
||||
```
|
||||
|
||||
## Struttura Repository
|
||||
|
||||
```
|
||||
ns7zip/
|
||||
├── build_plugin.py # Script di build unificato (tutte le versioni)
|
||||
├── rebuild_nsis7z1900-src.ps1 # Ricostruisce sorgenti 19.00
|
||||
├── 7zip-19.00/ # 7-Zip 19.00 modificato
|
||||
├── 7zip-25.01/ # 7-Zip 25.01 modificato (ZIP + NSIS handler)
|
||||
└── 7zip-26.00/ # 7-Zip 26.00 modificato (ZIP + NSIS handler)
|
||||
```
|
||||
|
||||
## Modifiche rispetto all'originale
|
||||
|
||||
### NSIS Archive Handler (25.01, 26.00)
|
||||
Aggiunto supporto per estrarre archivi `.exe` creati con NSIS (non presente nel plugin originale):
|
||||
- `Archive\Nsis\NsisHandler.cpp/h`
|
||||
- `Archive\Nsis\NsisIn.cpp/h`
|
||||
- `Archive\Nsis\NsisDecode.cpp/h`
|
||||
- `Archive\Nsis\NsisRegister.cpp`
|
||||
- `Compress\BZip2Decoder.cpp/h` (richiesto da NsisDecode)
|
||||
- `Compress\BZip2Crc.cpp`
|
||||
|
||||
### Bug Fix (25.01, 26.00)
|
||||
- **Divide-by-zero**: aggiunto guard `if (totalSize == 0) return 0` in `GetPercentComplete`
|
||||
- **ExtractWithFileCallback**: callback NSIS triggerata solo al cambio filename (evita crash con `totalSize = UINT64_MAX` alla prima chiamata `SetTotal`)
|
||||
|
||||
## License
|
||||
|
||||
- **7-Zip**: LGPL 2.1 + BSD 3-clause
|
||||
- **Plugin NSIS**: Afrow UK
|
||||
|
||||
## Credits
|
||||
|
||||
- Igor Pavlov (7-Zip)
|
||||
- Afrow UK (Plugin originale)
|
||||
- Simone (Supporto x64, VS2022/VS2026, ZIP, NSIS handler, ExtractWithFileCallback)
|
||||
@@ -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%
|
||||
+33
-5
@@ -5,27 +5,41 @@ Wraps per-version/per-toolset build scripts.
|
||||
Defaults: 7-Zip 26.00, VS2026 (v145 toolset)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, subprocess, sys
|
||||
import argparse, platform, subprocess, sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Colors:
|
||||
CYAN = "\033[36m"; GREEN = "\033[32m"; YELLOW = "\033[33m"
|
||||
RED = "\033[31m"; GRAY = "\033[90m"; RESET = "\033[0m"
|
||||
BOLD = "\033[1m"; BRIGHT_CYAN = "\033[96m"
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
SCRIPTS = {
|
||||
WINDOWS_SCRIPTS = {
|
||||
"19.00": {"2022": "tools/legacy/build_plugin_vs2022.py",
|
||||
"2026": "tools/legacy/build_plugin_vs2026.py"},
|
||||
"25.01": {"2022": "tools/legacy/build_plugin_2501_vs2022.py",
|
||||
"2026": "tools/legacy/build_plugin_2501_vs2026.py"},
|
||||
"26.00": {"2022": None,
|
||||
"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:
|
||||
parser = argparse.ArgumentParser(description="Build nsis7z NSIS plugin")
|
||||
parser.add_argument("--7zip-version", dest="zip_version",
|
||||
choices=["19.00", "25.01", "26.00"], default="26.00",
|
||||
help="7-Zip version to build (default: 26.00)")
|
||||
choices=["19.00", "25.01", "26.00", "zstd"], 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",
|
||||
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",
|
||||
help="Print plugin version and exit")
|
||||
known, rest = parser.parse_known_args()
|
||||
@@ -34,11 +48,25 @@ def main() -> int:
|
||||
print((ROOT / "VERSION").read_text(encoding="utf-8-sig").strip())
|
||||
return 0
|
||||
|
||||
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} "
|
||||
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
|
||||
if toolset == "auto":
|
||||
toolset = "2026"
|
||||
|
||||
script_rel = SCRIPTS[known.zip_version][toolset]
|
||||
script_rel = WINDOWS_SCRIPTS[known.zip_version][toolset]
|
||||
if script_rel is None:
|
||||
print(f"ERROR: no {known.zip_version} script for toolset {toolset}", file=sys.stderr)
|
||||
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.
@@ -0,0 +1,941 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script for nsis7z plugin (7-Zip 25.01 version)
|
||||
|
||||
This script builds the nsis7z NSIS plugin from 7-Zip 25.01 sources for multiple configurations.
|
||||
|
||||
Configurations:
|
||||
- x86-unicode: 32-bit NSIS Unicode (most common)
|
||||
- x64-unicode: 64-bit NSIS Unicode
|
||||
- x86-ansi: 32-bit NSIS ANSI (legacy)
|
||||
|
||||
Requirements:
|
||||
- Visual Studio 2022 with C++ desktop workload
|
||||
- Windows 10 SDK
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
import threading
|
||||
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
|
||||
class BuildConfig:
|
||||
"""Build configuration settings"""
|
||||
name: str # Configuration identifier
|
||||
platform: str # MSBuild platform (Win32/x64)
|
||||
config: str # MSBuild configuration name
|
||||
dest_dir: str # Destination directory in plugins folder
|
||||
|
||||
|
||||
# Build configurations mapping
|
||||
CONFIGS = {
|
||||
'x86-unicode': BuildConfig('x86-unicode', 'Win32', 'Release Unicode', 'x86-unicode'),
|
||||
'x64-unicode': BuildConfig('x64-unicode', 'x64', 'Release Unicode', 'amd64-unicode'),
|
||||
'x86-ansi': BuildConfig('x86-ansi', 'Win32', 'Release', '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),
|
||||
'-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
|
||||
|
||||
|
||||
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:
|
||||
base_path = Path(rf'C:\Program Files\Microsoft Visual Studio\{version}')
|
||||
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() -> Tuple[Path, Path, Path]:
|
||||
"""Get paths to project directory, project file, and plugins output"""
|
||||
|
||||
script_dir = Path(__file__).parent.resolve()
|
||||
|
||||
# Project directory containing 7-Zip 25.01 sources
|
||||
project_dir = script_dir.parent.parent / 'versions' / '25.01'
|
||||
|
||||
# Project file path
|
||||
project_file = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Nsis7z.vcxproj'
|
||||
|
||||
# Plugins output directory
|
||||
plugins_dir = script_dir.parent.parent / 'plugins'
|
||||
|
||||
return project_dir, project_file, plugins_dir
|
||||
|
||||
|
||||
def get_cpu_info() -> dict:
|
||||
"""Get CPU core count information"""
|
||||
try:
|
||||
import psutil
|
||||
logical = psutil.cpu_count(logical=True)
|
||||
physical = psutil.cpu_count(logical=False)
|
||||
return {
|
||||
'logical_cores': logical,
|
||||
'physical_cores': physical,
|
||||
'has_hyperthreading': logical > physical,
|
||||
'has_psutil': True
|
||||
}
|
||||
except ImportError:
|
||||
# Fallback without psutil
|
||||
import multiprocessing
|
||||
cores = multiprocessing.cpu_count()
|
||||
return {
|
||||
'logical_cores': cores,
|
||||
'physical_cores': cores,
|
||||
'has_hyperthreading': False,
|
||||
'has_psutil': False
|
||||
}
|
||||
|
||||
|
||||
def get_optimal_thread_count() -> int:
|
||||
"""Calculate optimal thread count for compilation"""
|
||||
cpu_info = get_cpu_info()
|
||||
|
||||
# Use physical cores for optimal performance (avoids hyperthreading overhead in compilation)
|
||||
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||
return cpu_info['physical_cores']
|
||||
else:
|
||||
return cpu_info['logical_cores']
|
||||
|
||||
|
||||
def get_build_optimizations() -> List[str]:
|
||||
"""Get additional build optimization flags"""
|
||||
return [
|
||||
'/p:BuildInParallel=true', # Enable parallel project builds
|
||||
'/p:MultiProcessorCompilation=true', # Enable MP compilation
|
||||
'/p:PreferredToolArchitecture=x64', # Use 64-bit tools (faster)
|
||||
'/p:UseSharedCompilation=true', # Enable shared compilation
|
||||
'/p:TrackFileAccess=false', # Disable file tracking (faster)
|
||||
'/nodeReuse:true', # Reuse MSBuild nodes
|
||||
'/p:GenerateResourceUsePreserializedResources=true' # Faster resources
|
||||
]
|
||||
|
||||
|
||||
def get_memory_optimizations() -> List[str]:
|
||||
"""Get memory optimization flags for faster builds"""
|
||||
try:
|
||||
import psutil
|
||||
# Get available memory in GB
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
|
||||
if memory_gb >= 16:
|
||||
# High memory system - aggressive caching
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false',
|
||||
'/p:BuildProjectReferences=true',
|
||||
'/p:UseCommonOutputDirectory=false'
|
||||
]
|
||||
elif memory_gb >= 8:
|
||||
# Medium memory system
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false'
|
||||
]
|
||||
else:
|
||||
# Low memory system - conservative
|
||||
return []
|
||||
except ImportError:
|
||||
# Conservative defaults without psutil
|
||||
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
|
||||
) -> Tuple[bool, float, str]:
|
||||
"""Build a single configuration
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, elapsed_time: float, captured_output: str)
|
||||
"""
|
||||
|
||||
# Build MSBuild command
|
||||
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',
|
||||
'/p:PlatformToolset=v143',
|
||||
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}'
|
||||
])
|
||||
|
||||
# Add build optimizations
|
||||
if optimizations:
|
||||
cmd.extend(get_build_optimizations())
|
||||
cmd.extend(get_memory_optimizations())
|
||||
|
||||
# Add cache directory if available
|
||||
cache_dir = setup_build_cache()
|
||||
if cache_dir:
|
||||
cmd.append(f'/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}")
|
||||
|
||||
# Execute build and measure time
|
||||
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:
|
||||
Tuple of (success: bool, file_size: int, dest_path: Optional[Path])
|
||||
"""
|
||||
|
||||
# Output is in Build\{config.name}\ relative to project directory
|
||||
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
|
||||
|
||||
# Create destination directory
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy file
|
||||
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'
|
||||
|
||||
# Collect unique output directories
|
||||
dirs_to_clean = set()
|
||||
for config in configs:
|
||||
output_dir = build_base_dir / config.name
|
||||
dirs_to_clean.add(output_dir)
|
||||
|
||||
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 time in seconds to human readable format"""
|
||||
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-2501'
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(cache_dir)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_key() -> bool:
|
||||
"""Wait for user to press a key. Returns False if 'q' is pressed."""
|
||||
print("\nPress any key to continue, or 'q' to quit...")
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
key = msvcrt.getch()
|
||||
return key.lower() not in (b'q', b'Q')
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
key = sys.stdin.read(1)
|
||||
return key.lower() not in ('q', 'Q')
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
except Exception:
|
||||
input() # Fallback to input()
|
||||
return True
|
||||
|
||||
|
||||
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||
"""Parse .vcxproj file to get available configurations
|
||||
|
||||
Returns:
|
||||
List of (Configuration, Platform) tuples
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(project_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# XML namespace for MSBuild project files
|
||||
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:
|
||||
# Format is "Configuration|Platform"
|
||||
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)
|
||||
|
||||
# Group by configuration name
|
||||
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()
|
||||
|
||||
# Show which ones are mapped in this script
|
||||
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,
|
||||
) -> list:
|
||||
"""Build all configurations simultaneously, printing output atomically.
|
||||
|
||||
Returns list of (config, success, build_time, file_size, dest_path) tuples
|
||||
in the same order as *configs*.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
import io
|
||||
import 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 for NSIS (7-Zip 25.01 version)',
|
||||
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 # Build specific configs
|
||||
%(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
|
||||
%(prog)s --pause # Wait for key press at the end
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
help='Force full rebuild (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,
|
||||
help='Enable parallel build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-parallel',
|
||||
action='store_false',
|
||||
dest='parallel',
|
||||
help='Disable parallel build'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbosity',
|
||||
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||
default='quiet',
|
||||
help='MSBuild verbosity level (default: quiet)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--clean',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help='Clean build artifacts after successful build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-clean',
|
||||
action='store_false',
|
||||
dest='clean',
|
||||
help='Do not clean build artifacts'
|
||||
)
|
||||
|
||||
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 available in the project file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--pause',
|
||||
action='store_true',
|
||||
help='Pause and wait for key press at the end'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-optimizations',
|
||||
action='store_true',
|
||||
help='Disable additional build optimizations'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--benchmark',
|
||||
action='store_true',
|
||||
help='Run performance comparison (with/without optimizations)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--vs-version',
|
||||
choices=['auto', '2026', '2022'],
|
||||
default='auto',
|
||||
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get project paths early for list-project
|
||||
project_dir, project_file, plugins_dir = get_project_paths()
|
||||
|
||||
# List project configurations
|
||||
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
|
||||
|
||||
# List script configurations
|
||||
if args.list:
|
||||
print("Build script configurations (7-Zip 25.01):")
|
||||
print("-" * 60)
|
||||
for name, config in CONFIGS.items():
|
||||
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||
print("\nUse --list-project to see all configurations in the .vcxproj file")
|
||||
return 0
|
||||
|
||||
# Find MSBuild
|
||||
msbuild_result = find_msbuild(args.vs_version)
|
||||
if not msbuild_result:
|
||||
print("ERROR: MSBuild not found!")
|
||||
print("Please install Visual Studio 2022 or 2026 with C++ workload.")
|
||||
return 1
|
||||
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||
|
||||
if not project_file.exists():
|
||||
print(f"ERROR: Project file not found: {project_file}")
|
||||
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 25.01){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()
|
||||
|
||||
# Print CPU and parallel build info
|
||||
use_optimizations = not args.no_optimizations
|
||||
print_cpu_info(args.parallel, use_optimizations)
|
||||
|
||||
# Benchmark mode - compare with and without optimizations
|
||||
if args.benchmark and len(configs_to_build) == 1:
|
||||
config = configs_to_build[0]
|
||||
print("=" * 50)
|
||||
print("BENCHMARK MODE - Comparing optimization strategies")
|
||||
print("=" * 50)
|
||||
|
||||
# Test without optimizations
|
||||
print("\n[1/2] Building WITHOUT optimizations...")
|
||||
success1, time1, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=False
|
||||
)
|
||||
|
||||
if success1:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Test with optimizations
|
||||
print("\n[2/2] Building WITH optimizations...")
|
||||
success2, time2, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=True
|
||||
)
|
||||
|
||||
if success2:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Show comparison
|
||||
print("\n" + "=" * 50)
|
||||
print("BENCHMARK RESULTS")
|
||||
print("=" * 50)
|
||||
print(f"Without optimizations: {format_time(time1)}")
|
||||
print(f"With optimizations: {format_time(time2)}")
|
||||
if time1 > time2:
|
||||
speedup = time1 / time2
|
||||
improvement = ((time1 - time2) / time1) * 100
|
||||
print(f"Speedup: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
|
||||
else:
|
||||
slowdown = time2 / time1
|
||||
print(f"Slowdown: {slowdown:.2f}x slower")
|
||||
|
||||
if args.clean:
|
||||
clean_build_artifacts(project_dir, configs_to_build)
|
||||
|
||||
return 0 if (success1 and success2) else 1
|
||||
|
||||
# Build each configuration
|
||||
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,
|
||||
)
|
||||
else:
|
||||
for i, config in enumerate(configs_to_build, 1):
|
||||
# Build
|
||||
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)}"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# Copy output
|
||||
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))
|
||||
|
||||
# Calculate total time
|
||||
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 if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||
print(f" - {dest}")
|
||||
|
||||
# Clean up
|
||||
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}")
|
||||
|
||||
# Show timing summary
|
||||
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()
|
||||
|
||||
# Pause if requested
|
||||
if args.pause:
|
||||
if not wait_for_key():
|
||||
print("Exiting...")
|
||||
return 1
|
||||
|
||||
return 0 if all_success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,968 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script for nsis7z plugin (7-Zip 25.01 version) - Visual Studio 2026
|
||||
Supports multiple build configurations with flexible parameters
|
||||
Uses PlatformToolset v145 (Visual Studio 2026 Build Tools)
|
||||
|
||||
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() -> 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' / '25.01'
|
||||
project_file = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Nsis7z_vs2026.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:
|
||||
# Fallback if psutil is not available
|
||||
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()
|
||||
|
||||
# Use physical cores for optimal performance (avoids hyperthreading overhead in compilation)
|
||||
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||
return cpu_info['physical_cores']
|
||||
else:
|
||||
return cpu_info['logical_cores']
|
||||
|
||||
|
||||
def get_build_optimizations() -> List[str]:
|
||||
"""Get additional build optimization flags"""
|
||||
return [
|
||||
'/p:BuildInParallel=true', # Enable parallel project builds
|
||||
'/p:MultiProcessorCompilation=true', # Enable MP compilation
|
||||
'/p:PreferredToolArchitecture=x64', # Use 64-bit tools (faster)
|
||||
'/p:UseSharedCompilation=true', # Enable shared compilation
|
||||
'/p:TrackFileAccess=false', # Disable file tracking (faster)
|
||||
'/nodeReuse:true', # Reuse MSBuild nodes
|
||||
'/p:GenerateResourceUsePreserializedResources=true' # Faster resources
|
||||
]
|
||||
|
||||
|
||||
def get_memory_optimizations() -> List[str]:
|
||||
"""Get memory optimization flags for faster builds"""
|
||||
try:
|
||||
import psutil
|
||||
# Get available memory in GB
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
|
||||
if memory_gb >= 16:
|
||||
# High memory system - aggressive caching
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false',
|
||||
'/p:BuildProjectReferences=true',
|
||||
'/p:UseCommonOutputDirectory=false'
|
||||
]
|
||||
elif memory_gb >= 8:
|
||||
# Medium memory system
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false'
|
||||
]
|
||||
else:
|
||||
# Low memory system - conservative
|
||||
return []
|
||||
except ImportError:
|
||||
# Conservative defaults without psutil
|
||||
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
|
||||
) -> Tuple[bool, float, str]:
|
||||
"""Build a single configuration
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, elapsed_time: float, captured_output: str)
|
||||
"""
|
||||
|
||||
# Build MSBuild command
|
||||
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',
|
||||
'/p:PlatformToolset=v145',
|
||||
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}'
|
||||
])
|
||||
|
||||
# Add build optimizations
|
||||
if optimizations:
|
||||
cmd.extend(get_build_optimizations())
|
||||
cmd.extend(get_memory_optimizations())
|
||||
|
||||
# Add cache directory if available
|
||||
cache_dir = setup_build_cache()
|
||||
if cache_dir:
|
||||
cmd.append(f'/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}")
|
||||
|
||||
# Execute build and measure time
|
||||
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:
|
||||
Tuple of (success: bool, file_size: int, dest_path: Optional[Path])
|
||||
"""
|
||||
|
||||
# Output is in Build\{config.name}\ relative to project directory
|
||||
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
|
||||
|
||||
# Create destination directory
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy file
|
||||
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'
|
||||
|
||||
# Collect unique output directories
|
||||
dirs_to_clean = set()
|
||||
for config in configs:
|
||||
output_dir = build_base_dir / config.name
|
||||
dirs_to_clean.add(output_dir)
|
||||
|
||||
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 time in seconds to human readable format"""
|
||||
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-2501-vs2026'
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(cache_dir)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_key() -> bool:
|
||||
"""Wait for user to press a key. Returns False if 'q' is pressed."""
|
||||
print("\nPress any key to continue, or 'q' to quit...")
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
key = msvcrt.getch()
|
||||
return key.lower() not in (b'q', b'Q')
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
key = sys.stdin.read(1)
|
||||
return key.lower() not in ('q', 'Q')
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
except Exception:
|
||||
input() # Fallback to input()
|
||||
return True
|
||||
|
||||
|
||||
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||
"""Parse .vcxproj file to get available configurations
|
||||
|
||||
Returns:
|
||||
List of (Configuration, Platform) tuples
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(project_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# XML namespace for MSBuild project files
|
||||
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:
|
||||
# Format is "Configuration|Platform"
|
||||
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)
|
||||
|
||||
# Group by configuration name
|
||||
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()
|
||||
|
||||
# Show which ones are mapped in this script
|
||||
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,
|
||||
) -> list:
|
||||
"""Build all configurations simultaneously, printing output atomically.
|
||||
|
||||
Returns list of (config, success, build_time, file_size, dest_path) tuples
|
||||
in the same order as *configs*.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
import io
|
||||
import 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 for NSIS (7-Zip 25.01, 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 # Build specific configs
|
||||
%(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
|
||||
%(prog)s --pause # Wait for key press at the end
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
help='Force full rebuild (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,
|
||||
help='Enable parallel build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-parallel',
|
||||
action='store_false',
|
||||
dest='parallel',
|
||||
help='Disable parallel build'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbosity',
|
||||
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||
default='quiet',
|
||||
help='MSBuild verbosity level (default: quiet)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--clean',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help='Clean build artifacts after successful build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-clean',
|
||||
action='store_false',
|
||||
dest='clean',
|
||||
help='Do not clean build artifacts'
|
||||
)
|
||||
|
||||
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 available in the project file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--pause',
|
||||
action='store_true',
|
||||
help='Pause and wait for key press at the end'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-optimizations',
|
||||
action='store_true',
|
||||
help='Disable additional build optimizations'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--benchmark',
|
||||
action='store_true',
|
||||
help='Run performance comparison (with/without optimizations)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--vs-version',
|
||||
choices=['auto', '2026', '2022'],
|
||||
default='auto',
|
||||
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get project paths early for list-project
|
||||
project_dir, project_file, plugins_dir = get_project_paths()
|
||||
|
||||
# List project configurations
|
||||
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
|
||||
|
||||
# List script configurations
|
||||
if args.list:
|
||||
print("Build script configurations (7-Zip 25.01, VS2026):")
|
||||
print("-" * 60)
|
||||
for name, config in CONFIGS.items():
|
||||
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||
print("\nUse --list-project to see all configurations in the .vcxproj file")
|
||||
return 0
|
||||
|
||||
# Find MSBuild
|
||||
msbuild_result = find_msbuild(args.vs_version)
|
||||
if not msbuild_result:
|
||||
print("ERROR: MSBuild not found!")
|
||||
print()
|
||||
print("Visual Studio 2026 Build Tools not found.")
|
||||
print("Checked locations:")
|
||||
print(" - C:\\Program Files\\Microsoft Visual Studio\\2026\\")
|
||||
print(" - C:\\Program Files (x86)\\Microsoft Visual Studio\\18\\")
|
||||
print(" - C:\\BuildTools\\")
|
||||
print()
|
||||
print("To install VS2026 Build Tools:")
|
||||
print(" choco install visualstudio2026buildtools")
|
||||
return 1
|
||||
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||
|
||||
if not project_file.exists():
|
||||
print(f"ERROR: Project file not found: {project_file}")
|
||||
print()
|
||||
print("You need to create the VS2026 project file first.")
|
||||
print("Copy Nsis7z.vcxproj to Nsis7z_vs2026.vcxproj and update the toolset.")
|
||||
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 25.01, 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()
|
||||
|
||||
# Print CPU and parallel build info
|
||||
use_optimizations = not args.no_optimizations
|
||||
print_cpu_info(args.parallel, use_optimizations)
|
||||
|
||||
# Benchmark mode - compare with and without optimizations
|
||||
if args.benchmark and len(configs_to_build) == 1:
|
||||
config = configs_to_build[0]
|
||||
print("=" * 50)
|
||||
print("BENCHMARK MODE - Comparing optimization strategies")
|
||||
print("=" * 50)
|
||||
|
||||
# Test without optimizations
|
||||
print("\n[1/2] Building WITHOUT optimizations...")
|
||||
success1, time1, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=False
|
||||
)
|
||||
|
||||
if success1:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Test with optimizations
|
||||
print("\n[2/2] Building WITH optimizations...")
|
||||
success2, time2, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=True
|
||||
)
|
||||
|
||||
if success2:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Show comparison
|
||||
print("\n" + "=" * 50)
|
||||
print("BENCHMARK RESULTS")
|
||||
print("=" * 50)
|
||||
print(f"Without optimizations: {format_time(time1)}")
|
||||
print(f"With optimizations: {format_time(time2)}")
|
||||
if time1 > time2:
|
||||
speedup = time1 / time2
|
||||
improvement = ((time1 - time2) / time1) * 100
|
||||
print(f"Speedup: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
|
||||
else:
|
||||
slowdown = time2 / time1
|
||||
print(f"Slowdown: {slowdown:.2f}x slower")
|
||||
|
||||
if args.clean:
|
||||
clean_build_artifacts(project_dir, configs_to_build)
|
||||
|
||||
return 0 if (success1 and success2) else 1
|
||||
|
||||
# Build each configuration
|
||||
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,
|
||||
)
|
||||
else:
|
||||
for i, config in enumerate(configs_to_build, 1):
|
||||
# Build
|
||||
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)}"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# Copy output
|
||||
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))
|
||||
|
||||
# Calculate total time
|
||||
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 if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||
print(f" - {dest}")
|
||||
|
||||
# Clean up
|
||||
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}")
|
||||
|
||||
# Show timing summary
|
||||
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()
|
||||
|
||||
# Pause if requested
|
||||
if args.pause:
|
||||
if not wait_for_key():
|
||||
print("Exiting...")
|
||||
return 1
|
||||
|
||||
return 0 if all_success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,967 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script for nsis7z plugin (7-Zip 26.00 version) - Visual Studio 2026
|
||||
Supports multiple build configurations with flexible parameters
|
||||
Uses PlatformToolset v145 (Visual Studio 2026 Build Tools)
|
||||
|
||||
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' / '26.00'
|
||||
vcxproj = 'Nsis7z_vs2026.vcxproj' if toolset == 'v145' else 'Nsis7z.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:
|
||||
# Fallback if psutil is not available
|
||||
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()
|
||||
|
||||
# Use physical cores for optimal performance (avoids hyperthreading overhead in compilation)
|
||||
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||
return cpu_info['physical_cores']
|
||||
else:
|
||||
return cpu_info['logical_cores']
|
||||
|
||||
|
||||
def get_build_optimizations() -> List[str]:
|
||||
"""Get additional build optimization flags"""
|
||||
return [
|
||||
'/p:BuildInParallel=true', # Enable parallel project builds
|
||||
'/p:MultiProcessorCompilation=true', # Enable MP compilation
|
||||
'/p:PreferredToolArchitecture=x64', # Use 64-bit tools (faster)
|
||||
'/p:UseSharedCompilation=true', # Enable shared compilation
|
||||
'/p:TrackFileAccess=false', # Disable file tracking (faster)
|
||||
'/nodeReuse:true', # Reuse MSBuild nodes
|
||||
'/p:GenerateResourceUsePreserializedResources=true' # Faster resources
|
||||
]
|
||||
|
||||
|
||||
def get_memory_optimizations() -> List[str]:
|
||||
"""Get memory optimization flags for faster builds"""
|
||||
try:
|
||||
import psutil
|
||||
# Get available memory in GB
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
|
||||
if memory_gb >= 16:
|
||||
# High memory system - aggressive caching
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false',
|
||||
'/p:BuildProjectReferences=true',
|
||||
'/p:UseCommonOutputDirectory=false'
|
||||
]
|
||||
elif memory_gb >= 8:
|
||||
# Medium memory system
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false'
|
||||
]
|
||||
else:
|
||||
# Low memory system - conservative
|
||||
return []
|
||||
except ImportError:
|
||||
# Conservative defaults without psutil
|
||||
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:
|
||||
Tuple of (success: bool, elapsed_time: float, captured_output: str)
|
||||
"""
|
||||
|
||||
# Build MSBuild command
|
||||
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}'
|
||||
])
|
||||
|
||||
# Add build optimizations
|
||||
if optimizations:
|
||||
cmd.extend(get_build_optimizations())
|
||||
cmd.extend(get_memory_optimizations())
|
||||
|
||||
# Add cache directory if available
|
||||
cache_dir = setup_build_cache()
|
||||
if cache_dir:
|
||||
cmd.append(f'/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}")
|
||||
|
||||
# Execute build and measure time
|
||||
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:
|
||||
Tuple of (success: bool, file_size: int, dest_path: Optional[Path])
|
||||
"""
|
||||
|
||||
# Output is in Build\{config.name}\ relative to project directory
|
||||
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
|
||||
|
||||
# Create destination directory
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy file
|
||||
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'
|
||||
|
||||
# Collect unique output directories
|
||||
dirs_to_clean = set()
|
||||
for config in configs:
|
||||
output_dir = build_base_dir / config.name
|
||||
dirs_to_clean.add(output_dir)
|
||||
|
||||
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 time in seconds to human readable format"""
|
||||
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-2600-vs2026'
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(cache_dir)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_key() -> bool:
|
||||
"""Wait for user to press a key. Returns False if 'q' is pressed."""
|
||||
print("\nPress any key to continue, or 'q' to quit...")
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
key = msvcrt.getch()
|
||||
return key.lower() not in (b'q', b'Q')
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
key = sys.stdin.read(1)
|
||||
return key.lower() not in ('q', 'Q')
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
except Exception:
|
||||
input() # Fallback to input()
|
||||
return True
|
||||
|
||||
|
||||
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||
"""Parse .vcxproj file to get available configurations
|
||||
|
||||
Returns:
|
||||
List of (Configuration, Platform) tuples
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(project_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# XML namespace for MSBuild project files
|
||||
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:
|
||||
# Format is "Configuration|Platform"
|
||||
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)
|
||||
|
||||
# Group by configuration name
|
||||
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()
|
||||
|
||||
# Show which ones are mapped in this script
|
||||
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.
|
||||
|
||||
Returns list of (config, success, build_time, file_size, dest_path) tuples
|
||||
in the same order as *configs*.
|
||||
"""
|
||||
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
|
||||
import 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 for NSIS (7-Zip 26.00, 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 # Build specific configs
|
||||
%(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
|
||||
%(prog)s --pause # Wait for key press at the end
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
help='Force full rebuild (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,
|
||||
help='Enable parallel build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-parallel',
|
||||
action='store_false',
|
||||
dest='parallel',
|
||||
help='Disable parallel build'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbosity',
|
||||
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||
default='quiet',
|
||||
help='MSBuild verbosity level (default: quiet)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--clean',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help='Clean build artifacts after successful build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-clean',
|
||||
action='store_false',
|
||||
dest='clean',
|
||||
help='Do not clean build artifacts'
|
||||
)
|
||||
|
||||
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 available in the project file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--pause',
|
||||
action='store_true',
|
||||
help='Pause and wait for key press at the end'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-optimizations',
|
||||
action='store_true',
|
||||
help='Disable additional build optimizations'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--benchmark',
|
||||
action='store_true',
|
||||
help='Run performance comparison (with/without optimizations)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--vs-version',
|
||||
choices=['auto', '2026', '2022'],
|
||||
default='auto',
|
||||
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Find MSBuild early (needed for project path selection)
|
||||
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 file depends on toolset)
|
||||
project_dir, project_file, plugins_dir = get_project_paths(platform_toolset)
|
||||
|
||||
# List project configurations
|
||||
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
|
||||
|
||||
# List script configurations
|
||||
if args.list:
|
||||
print("Build script configurations (7-Zip 26.00, VS2026):")
|
||||
print("-" * 60)
|
||||
for name, config in CONFIGS.items():
|
||||
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||
print("\nUse --list-project to see all configurations in the .vcxproj file")
|
||||
return 0
|
||||
|
||||
# Find MSBuild (already done above, but keep for reference)
|
||||
if not project_file.exists():
|
||||
print(f"ERROR: Project file not found: {project_file}")
|
||||
print()
|
||||
print("You need to create the VS2026 project file first.")
|
||||
print("Copy Nsis7z.vcxproj to Nsis7z_vs2026.vcxproj and update the toolset.")
|
||||
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 26.00, 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()
|
||||
|
||||
# Print CPU and parallel build info
|
||||
use_optimizations = not args.no_optimizations
|
||||
print_cpu_info(args.parallel, use_optimizations)
|
||||
|
||||
# Benchmark mode - compare with and without optimizations
|
||||
if args.benchmark and len(configs_to_build) == 1:
|
||||
config = configs_to_build[0]
|
||||
print("=" * 50)
|
||||
print("BENCHMARK MODE - Comparing optimization strategies")
|
||||
print("=" * 50)
|
||||
|
||||
# Test without optimizations
|
||||
print("\n[1/2] Building WITHOUT optimizations...")
|
||||
success1, time1, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=False,
|
||||
toolset=platform_toolset,
|
||||
)
|
||||
|
||||
if success1:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Test with optimizations
|
||||
print("\n[2/2] Building WITH optimizations...")
|
||||
success2, time2, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=True,
|
||||
toolset=platform_toolset,
|
||||
)
|
||||
|
||||
if success2:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Show comparison
|
||||
print("\n" + "=" * 50)
|
||||
print("BENCHMARK RESULTS")
|
||||
print("=" * 50)
|
||||
print(f"Without optimizations: {format_time(time1)}")
|
||||
print(f"With optimizations: {format_time(time2)}")
|
||||
if time1 > time2:
|
||||
speedup = time1 / time2
|
||||
improvement = ((time1 - time2) / time1) * 100
|
||||
print(f"Speedup: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
|
||||
else:
|
||||
slowdown = time2 / time1
|
||||
print(f"Slowdown: {slowdown:.2f}x slower")
|
||||
|
||||
if args.clean:
|
||||
clean_build_artifacts(project_dir, configs_to_build)
|
||||
|
||||
return 0 if (success1 and success2) else 1
|
||||
|
||||
# Build each configuration
|
||||
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):
|
||||
# Build
|
||||
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
|
||||
|
||||
# Copy output
|
||||
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))
|
||||
|
||||
# Calculate total time
|
||||
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 if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||
print(f" - {dest}")
|
||||
|
||||
# Clean up
|
||||
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}")
|
||||
|
||||
# Show timing summary
|
||||
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()
|
||||
|
||||
# Pause if requested
|
||||
if args.pause:
|
||||
if not wait_for_key():
|
||||
print("Exiting...")
|
||||
return 1
|
||||
|
||||
return 0 if all_success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,941 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script for nsis7z plugin - All configurations
|
||||
Supports multiple build configurations with flexible parameters
|
||||
"""
|
||||
|
||||
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),
|
||||
'-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
|
||||
|
||||
|
||||
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:
|
||||
base_path = Path(rf'C:\Program Files\Microsoft Visual Studio\{version}')
|
||||
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() -> 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' / '19.00'
|
||||
project_file = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / '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:
|
||||
# Fallback if psutil is not available
|
||||
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()
|
||||
|
||||
# Use physical cores for optimal performance (avoids hyperthreading overhead in compilation)
|
||||
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||
return cpu_info['physical_cores']
|
||||
else:
|
||||
return cpu_info['logical_cores']
|
||||
|
||||
|
||||
def get_build_optimizations() -> List[str]:
|
||||
"""Get additional build optimization flags"""
|
||||
return [
|
||||
'/p:BuildInParallel=true', # Enable parallel project builds
|
||||
'/p:MultiProcessorCompilation=true', # Enable MP compilation
|
||||
'/p:PreferredToolArchitecture=x64', # Use 64-bit tools (faster)
|
||||
'/p:UseSharedCompilation=true', # Enable shared compilation
|
||||
'/p:TrackFileAccess=false', # Disable file tracking (faster)
|
||||
'/nodeReuse:true', # Reuse MSBuild nodes
|
||||
'/p:GenerateResourceUsePreserializedResources=true' # Faster resources
|
||||
]
|
||||
|
||||
|
||||
def get_memory_optimizations() -> List[str]:
|
||||
"""Get memory optimization flags for faster builds"""
|
||||
try:
|
||||
import psutil
|
||||
# Get available memory in GB
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
|
||||
if memory_gb >= 16:
|
||||
# High memory system - aggressive caching
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false',
|
||||
'/p:BuildProjectReferences=true',
|
||||
'/p:UseCommonOutputDirectory=false'
|
||||
]
|
||||
elif memory_gb >= 8:
|
||||
# Medium memory system
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false'
|
||||
]
|
||||
else:
|
||||
# Low memory system - conservative
|
||||
return []
|
||||
except ImportError:
|
||||
# Conservative defaults without psutil
|
||||
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
|
||||
) -> Tuple[bool, float, str]:
|
||||
"""Build a single configuration
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, elapsed_time: float, captured_output: str)
|
||||
"""
|
||||
|
||||
# Build MSBuild command
|
||||
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',
|
||||
'/p:PlatformToolset=v143',
|
||||
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}'
|
||||
])
|
||||
|
||||
# Add build optimizations
|
||||
if optimizations:
|
||||
cmd.extend(get_build_optimizations())
|
||||
cmd.extend(get_memory_optimizations())
|
||||
|
||||
# Add cache directory if available
|
||||
cache_dir = setup_build_cache()
|
||||
if cache_dir:
|
||||
cmd.append(f'/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}")
|
||||
|
||||
# Execute build and measure time
|
||||
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:
|
||||
Tuple of (success: bool, file_size: int, dest_path: Optional[Path])
|
||||
"""
|
||||
|
||||
# Output is in Build\{config.name}\ relative to project directory
|
||||
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
|
||||
|
||||
# Create destination directory
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy file
|
||||
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'
|
||||
|
||||
# Collect unique output directories
|
||||
dirs_to_clean = set()
|
||||
for config in configs:
|
||||
output_dir = build_base_dir / config.name
|
||||
dirs_to_clean.add(output_dir)
|
||||
|
||||
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 time in seconds to human readable format"""
|
||||
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'
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(cache_dir)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_key() -> bool:
|
||||
"""Wait for user to press a key. Returns False if 'q' is pressed."""
|
||||
print("\nPress any key to continue, or 'q' to quit...")
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
key = msvcrt.getch()
|
||||
return key.lower() not in (b'q', b'Q')
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
key = sys.stdin.read(1)
|
||||
return key.lower() not in ('q', 'Q')
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
except Exception:
|
||||
input() # Fallback to input()
|
||||
return True
|
||||
|
||||
|
||||
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||
"""Parse .vcxproj file to get available configurations
|
||||
|
||||
Returns:
|
||||
List of (Configuration, Platform) tuples
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(project_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# XML namespace for MSBuild project files
|
||||
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:
|
||||
# Format is "Configuration|Platform"
|
||||
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)
|
||||
|
||||
# Group by configuration name
|
||||
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()
|
||||
|
||||
# Show which ones are mapped in this script
|
||||
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,
|
||||
) -> list:
|
||||
"""Build all configurations simultaneously, printing output atomically.
|
||||
|
||||
Returns list of (config, success, build_time, file_size, dest_path) tuples
|
||||
in the same order as *configs*.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
import io
|
||||
import 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 for NSIS',
|
||||
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 # Build specific configs
|
||||
%(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
|
||||
%(prog)s --pause # Wait for key press at the end
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
help='Force full rebuild (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,
|
||||
help='Enable parallel build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-parallel',
|
||||
action='store_false',
|
||||
dest='parallel',
|
||||
help='Disable parallel build'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbosity',
|
||||
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||
default='quiet',
|
||||
help='MSBuild verbosity level (default: quiet)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--clean',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help='Clean build artifacts after successful build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-clean',
|
||||
action='store_false',
|
||||
dest='clean',
|
||||
help='Do not clean build artifacts'
|
||||
)
|
||||
|
||||
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 available in the project file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--pause',
|
||||
action='store_true',
|
||||
help='Pause and wait for key press at the end'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-optimizations',
|
||||
action='store_true',
|
||||
help='Disable additional build optimizations'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--benchmark',
|
||||
action='store_true',
|
||||
help='Run performance comparison (with/without optimizations)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--vs-version',
|
||||
choices=['auto', '2026', '2022'],
|
||||
default='auto',
|
||||
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get project paths early for list-project
|
||||
project_dir, project_file, plugins_dir = get_project_paths()
|
||||
|
||||
# List project configurations
|
||||
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
|
||||
|
||||
# List script configurations
|
||||
if args.list:
|
||||
print("Build script configurations:")
|
||||
print("-" * 60)
|
||||
for name, config in CONFIGS.items():
|
||||
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||
print("\nUse --list-project to see all configurations in the .vcxproj file")
|
||||
return 0
|
||||
|
||||
# Find MSBuild
|
||||
msbuild_result = find_msbuild(args.vs_version)
|
||||
if not msbuild_result:
|
||||
print("ERROR: MSBuild not found!")
|
||||
print("Please install Visual Studio 2022 or 2026 with C++ workload.")
|
||||
return 1
|
||||
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||
|
||||
if not project_file.exists():
|
||||
print(f"ERROR: Project file not found: {project_file}")
|
||||
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{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()
|
||||
|
||||
# Print CPU and parallel build info
|
||||
use_optimizations = not args.no_optimizations
|
||||
print_cpu_info(args.parallel, use_optimizations)
|
||||
|
||||
# Benchmark mode - compare with and without optimizations
|
||||
if args.benchmark and len(configs_to_build) == 1:
|
||||
config = configs_to_build[0]
|
||||
print("=" * 50)
|
||||
print("BENCHMARK MODE - Comparing optimization strategies")
|
||||
print("=" * 50)
|
||||
|
||||
# Test without optimizations
|
||||
print("\n[1/2] Building WITHOUT optimizations...")
|
||||
success1, time1, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=False
|
||||
)
|
||||
|
||||
if success1:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Test with optimizations
|
||||
print("\n[2/2] Building WITH optimizations...")
|
||||
success2, time2, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=True
|
||||
)
|
||||
|
||||
if success2:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Show comparison
|
||||
print("\n" + "=" * 50)
|
||||
print("BENCHMARK RESULTS")
|
||||
print("=" * 50)
|
||||
print(f"Without optimizations: {format_time(time1)}")
|
||||
print(f"With optimizations: {format_time(time2)}")
|
||||
if time1 > time2:
|
||||
speedup = time1 / time2
|
||||
improvement = ((time1 - time2) / time1) * 100
|
||||
print(f"Speedup: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
|
||||
else:
|
||||
slowdown = time2 / time1
|
||||
print(f"Slowdown: {slowdown:.2f}x slower")
|
||||
|
||||
if args.clean:
|
||||
clean_build_artifacts(project_dir, configs_to_build)
|
||||
|
||||
return 0 if (success1 and success2) else 1
|
||||
|
||||
# Build each configuration
|
||||
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,
|
||||
)
|
||||
else:
|
||||
for i, config in enumerate(configs_to_build, 1):
|
||||
# Build
|
||||
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)}"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# Copy output
|
||||
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))
|
||||
|
||||
# Calculate total time
|
||||
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 if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||
print(f" - {dest}")
|
||||
|
||||
# Clean up
|
||||
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}")
|
||||
|
||||
# Show timing summary
|
||||
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()
|
||||
|
||||
# Pause if requested
|
||||
if args.pause:
|
||||
if not wait_for_key():
|
||||
print("Exiting...")
|
||||
return 1
|
||||
|
||||
return 0 if all_success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,968 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script for nsis7z plugin - All configurations (Visual Studio 2026)
|
||||
Supports multiple build configurations with flexible parameters
|
||||
Uses PlatformToolset v145 (Visual Studio 2026 Build Tools)
|
||||
|
||||
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() -> 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' / '19.00'
|
||||
project_file = project_dir / 'CPP' / '7zip' / 'Bundles' / 'Nsis7z' / 'Nsis7z_vs2026.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:
|
||||
# Fallback if psutil is not available
|
||||
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()
|
||||
|
||||
# Use physical cores for optimal performance (avoids hyperthreading overhead in compilation)
|
||||
if cpu_info['has_hyperthreading'] and cpu_info['physical_cores'] > 1:
|
||||
return cpu_info['physical_cores']
|
||||
else:
|
||||
return cpu_info['logical_cores']
|
||||
|
||||
|
||||
def get_build_optimizations() -> List[str]:
|
||||
"""Get additional build optimization flags"""
|
||||
return [
|
||||
'/p:BuildInParallel=true', # Enable parallel project builds
|
||||
'/p:MultiProcessorCompilation=true', # Enable MP compilation
|
||||
'/p:PreferredToolArchitecture=x64', # Use 64-bit tools (faster)
|
||||
'/p:UseSharedCompilation=true', # Enable shared compilation
|
||||
'/p:TrackFileAccess=false', # Disable file tracking (faster)
|
||||
'/nodeReuse:true', # Reuse MSBuild nodes
|
||||
'/p:GenerateResourceUsePreserializedResources=true' # Faster resources
|
||||
]
|
||||
|
||||
|
||||
def get_memory_optimizations() -> List[str]:
|
||||
"""Get memory optimization flags for faster builds"""
|
||||
try:
|
||||
import psutil
|
||||
# Get available memory in GB
|
||||
memory_gb = psutil.virtual_memory().total / (1024**3)
|
||||
|
||||
if memory_gb >= 16:
|
||||
# High memory system - aggressive caching
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false',
|
||||
'/p:BuildProjectReferences=true',
|
||||
'/p:UseCommonOutputDirectory=false'
|
||||
]
|
||||
elif memory_gb >= 8:
|
||||
# Medium memory system
|
||||
return [
|
||||
'/p:DisableFastUpToDateCheck=false'
|
||||
]
|
||||
else:
|
||||
# Low memory system - conservative
|
||||
return []
|
||||
except ImportError:
|
||||
# Conservative defaults without psutil
|
||||
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
|
||||
) -> Tuple[bool, float, str]:
|
||||
"""Build a single configuration
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, elapsed_time: float, captured_output: str)
|
||||
"""
|
||||
|
||||
# Build MSBuild command
|
||||
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',
|
||||
'/p:PlatformToolset=v145',
|
||||
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}'
|
||||
])
|
||||
|
||||
# Add build optimizations
|
||||
if optimizations:
|
||||
cmd.extend(get_build_optimizations())
|
||||
cmd.extend(get_memory_optimizations())
|
||||
|
||||
# Add cache directory if available
|
||||
cache_dir = setup_build_cache()
|
||||
if cache_dir:
|
||||
cmd.append(f'/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}")
|
||||
|
||||
# Execute build and measure time
|
||||
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:
|
||||
Tuple of (success: bool, file_size: int, dest_path: Optional[Path])
|
||||
"""
|
||||
|
||||
# Output is in Build\{config.name}\ relative to project directory
|
||||
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
|
||||
|
||||
# Create destination directory
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy file
|
||||
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'
|
||||
|
||||
# Collect unique output directories
|
||||
dirs_to_clean = set()
|
||||
for config in configs:
|
||||
output_dir = build_base_dir / config.name
|
||||
dirs_to_clean.add(output_dir)
|
||||
|
||||
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 time in seconds to human readable format"""
|
||||
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-vs2026'
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(cache_dir)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_key() -> bool:
|
||||
"""Wait for user to press a key. Returns False if 'q' is pressed."""
|
||||
print("\nPress any key to continue, or 'q' to quit...")
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
key = msvcrt.getch()
|
||||
return key.lower() not in (b'q', b'Q')
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
key = sys.stdin.read(1)
|
||||
return key.lower() not in ('q', 'Q')
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
except Exception:
|
||||
input() # Fallback to input()
|
||||
return True
|
||||
|
||||
|
||||
def get_available_configurations(project_file: Path) -> List[Tuple[str, str]]:
|
||||
"""Parse .vcxproj file to get available configurations
|
||||
|
||||
Returns:
|
||||
List of (Configuration, Platform) tuples
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(project_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# XML namespace for MSBuild project files
|
||||
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:
|
||||
# Format is "Configuration|Platform"
|
||||
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)
|
||||
|
||||
# Group by configuration name
|
||||
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()
|
||||
|
||||
# Show which ones are mapped in this script
|
||||
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,
|
||||
) -> list:
|
||||
"""Build all configurations simultaneously, printing output atomically.
|
||||
|
||||
Returns list of (config, success, build_time, file_size, dest_path) tuples
|
||||
in the same order as *configs*.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
import io
|
||||
import 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 for NSIS (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 # Build specific configs
|
||||
%(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
|
||||
%(prog)s --pause # Wait for key press at the end
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
help='Force full rebuild (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,
|
||||
help='Enable parallel build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-parallel',
|
||||
action='store_false',
|
||||
dest='parallel',
|
||||
help='Disable parallel build'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbosity',
|
||||
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||
default='quiet',
|
||||
help='MSBuild verbosity level (default: quiet)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--clean',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help='Clean build artifacts after successful build (default: True)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-clean',
|
||||
action='store_false',
|
||||
dest='clean',
|
||||
help='Do not clean build artifacts'
|
||||
)
|
||||
|
||||
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 available in the project file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--pause',
|
||||
action='store_true',
|
||||
help='Pause and wait for key press at the end'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-optimizations',
|
||||
action='store_true',
|
||||
help='Disable additional build optimizations'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--benchmark',
|
||||
action='store_true',
|
||||
help='Run performance comparison (with/without optimizations)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--vs-version',
|
||||
choices=['auto', '2026', '2022'],
|
||||
default='auto',
|
||||
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get project paths early for list-project
|
||||
project_dir, project_file, plugins_dir = get_project_paths()
|
||||
|
||||
# List project configurations
|
||||
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
|
||||
|
||||
# List script configurations
|
||||
if args.list:
|
||||
print("Build script configurations (VS2026):")
|
||||
print("-" * 60)
|
||||
for name, config in CONFIGS.items():
|
||||
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||
print(f"\nTotal: {len(CONFIGS)} configuration(s)")
|
||||
print("\nUse --list-project to see all configurations in the .vcxproj file")
|
||||
return 0
|
||||
|
||||
# Find MSBuild
|
||||
msbuild_result = find_msbuild(args.vs_version)
|
||||
if not msbuild_result:
|
||||
print("ERROR: MSBuild not found!")
|
||||
print()
|
||||
print("Visual Studio 2026 Build Tools not found.")
|
||||
print("Checked locations:")
|
||||
print(" - C:\\Program Files\\Microsoft Visual Studio\\2026\\")
|
||||
print(" - C:\\Program Files (x86)\\Microsoft Visual Studio\\18\\")
|
||||
print(" - C:\\BuildTools\\")
|
||||
print()
|
||||
print("To install VS2026 Build Tools:")
|
||||
print(" choco install visualstudio2026buildtools")
|
||||
return 1
|
||||
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||
|
||||
if not project_file.exists():
|
||||
print(f"ERROR: Project file not found: {project_file}")
|
||||
print()
|
||||
print("You need to create the VS2026 project file first.")
|
||||
print("Copy Nsis7z.vcxproj to Nsis7z_vs2026.vcxproj and update the toolset.")
|
||||
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 (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()
|
||||
|
||||
# Print CPU and parallel build info
|
||||
use_optimizations = not args.no_optimizations
|
||||
print_cpu_info(args.parallel, use_optimizations)
|
||||
|
||||
# Benchmark mode - compare with and without optimizations
|
||||
if args.benchmark and len(configs_to_build) == 1:
|
||||
config = configs_to_build[0]
|
||||
print("=" * 50)
|
||||
print("BENCHMARK MODE - Comparing optimization strategies")
|
||||
print("=" * 50)
|
||||
|
||||
# Test without optimizations
|
||||
print("\n[1/2] Building WITHOUT optimizations...")
|
||||
success1, time1, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=False
|
||||
)
|
||||
|
||||
if success1:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Test with optimizations
|
||||
print("\n[2/2] Building WITH optimizations...")
|
||||
success2, time2, _ = build_configuration(
|
||||
msbuild_path, project_file, config,
|
||||
rebuild=True, verbosity=args.verbosity,
|
||||
parallel=args.parallel, optimizations=True
|
||||
)
|
||||
|
||||
if success2:
|
||||
copy_output(project_dir, plugins_dir, config)
|
||||
|
||||
# Show comparison
|
||||
print("\n" + "=" * 50)
|
||||
print("BENCHMARK RESULTS")
|
||||
print("=" * 50)
|
||||
print(f"Without optimizations: {format_time(time1)}")
|
||||
print(f"With optimizations: {format_time(time2)}")
|
||||
if time1 > time2:
|
||||
speedup = time1 / time2
|
||||
improvement = ((time1 - time2) / time1) * 100
|
||||
print(f"Speedup: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
|
||||
else:
|
||||
slowdown = time2 / time1
|
||||
print(f"Slowdown: {slowdown:.2f}x slower")
|
||||
|
||||
if args.clean:
|
||||
clean_build_artifacts(project_dir, configs_to_build)
|
||||
|
||||
return 0 if (success1 and success2) else 1
|
||||
|
||||
# Build each configuration
|
||||
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,
|
||||
)
|
||||
else:
|
||||
for i, config in enumerate(configs_to_build, 1):
|
||||
# Build
|
||||
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)}"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# Copy output
|
||||
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))
|
||||
|
||||
# Calculate total time
|
||||
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 if dest_path else plugins_dir / config.dest_dir / 'nsis7z.dll'
|
||||
print(f" - {dest}")
|
||||
|
||||
# Clean up
|
||||
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}")
|
||||
|
||||
# Show timing summary
|
||||
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()
|
||||
|
||||
# Pause if requested
|
||||
if args.pause:
|
||||
if not wait_for_key():
|
||||
print("Exiting...")
|
||||
return 1
|
||||
|
||||
return 0 if all_success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -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)
|
||||
{
|
||||
(void)lpReserved;
|
||||
g_hInstance2=(HINSTANCE)hInst;
|
||||
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
|
||||
@@ -27,12 +27,14 @@ using namespace NDir;
|
||||
|
||||
extern HWND g_hwndProgress;
|
||||
|
||||
static const UInt64 k_SizeUnknown = (UInt64)(Int64)-1;
|
||||
|
||||
void CExtractCallbackConsole::UpdateProgress()
|
||||
{
|
||||
// Prima controlla se c'è il callback con filename (nuova funzione)
|
||||
if (ProgressWithFileHandler != NULL)
|
||||
{
|
||||
if (completedSize != -1 || totalSize > 0)
|
||||
if (completedSize != k_SizeUnknown || totalSize > 0)
|
||||
ProgressWithFileHandler(completedSize, totalSize, CurrentFileName.Ptr());
|
||||
else
|
||||
ProgressWithFileHandler(0, 0, CurrentFileName.Ptr());
|
||||
@@ -40,14 +42,14 @@ void CExtractCallbackConsole::UpdateProgress()
|
||||
// Altrimenti usa il callback originale (per compatibilitĂ )
|
||||
else if (ProgressHandler != NULL)
|
||||
{
|
||||
if (completedSize != -1 || totalSize > 0)
|
||||
if (completedSize != k_SizeUnknown || totalSize > 0)
|
||||
ProgressHandler(completedSize, totalSize);
|
||||
else
|
||||
ProgressHandler(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 val)
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::SetTotal(UInt64 val))
|
||||
{
|
||||
totalSize = val;
|
||||
UpdateProgress();
|
||||
@@ -56,7 +58,7 @@ STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 val)
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *val)
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::SetCompleted(const UInt64 *val))
|
||||
{
|
||||
completedSize = *val;
|
||||
UpdateProgress();
|
||||
@@ -65,31 +67,35 @@ STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *val)
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::AskOverwrite(
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::AskOverwrite(
|
||||
const wchar_t *existName, const FILETIME *, const UInt64 *,
|
||||
const wchar_t *newName, const FILETIME *, const UInt64 *,
|
||||
Int32 *answer)
|
||||
Int32 *answer))
|
||||
{
|
||||
(void)existName; (void)newName;
|
||||
*answer = NOverwriteAnswer::kYesToAll;
|
||||
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
|
||||
CurrentFileName = name;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::MessageError(const wchar_t *message)
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::MessageError(const wchar_t *message))
|
||||
{
|
||||
(void)message;
|
||||
NumFileErrorsInCurrentArchive++;
|
||||
NumFileErrors++;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted)
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted))
|
||||
{
|
||||
(void)encrypted;
|
||||
switch(opRes)
|
||||
{
|
||||
case NArchive::NExtract::NOperationResult::kOK:
|
||||
@@ -113,7 +119,7 @@ HRESULT CExtractCallbackConsole::SetPassword(const UString &password)
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
|
||||
Z7_COM7F_IMF(CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password))
|
||||
{
|
||||
if (!PasswordIsDefined)
|
||||
{
|
||||
@@ -127,6 +133,7 @@ STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
|
||||
|
||||
HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
|
||||
{
|
||||
(void)name; (void)testMode;
|
||||
NumArchives++;
|
||||
NumFileErrorsInCurrentArchive = 0;
|
||||
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)
|
||||
{
|
||||
(void)codecs; (void)arcLink; (void)name;
|
||||
if (result != S_OK)
|
||||
{
|
||||
NumArchiveErrors++;
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
#ifndef __STDAFX_H
|
||||
#define __STDAFX_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <Windows.h>
|
||||
#include "7zip/Bundles/Nsis7z/pluginapi.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "../../Bundles/Nsis7z/pluginapi.h"
|
||||
|
||||
#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