14 Commits

Author SHA1 Message Date
Simone cdb61af358 feat(linux): per-config + total wall-clock build timing
Print a timing summary (each config status+seconds and total wall
clock) for both parallel and sequential paths, mirroring the Windows
build output. Helps compare Linux vs Windows and track perf changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:05:14 +02:00
Simone 8155d78230 fix(build): require VC C++ tools in vswhere query
Two 18.x installs coexisted: VS2026 Community (full IDE, has VC v180)
and a BuildTools at (x86)\Microsoft Visual Studio\18\BuildTools that
has MSBuild but NO VC v180 targets. The vswhere query only required
Microsoft.Component.MSBuild, so -latest could (and did) return the
C++-less BuildTools -> MSB4019. Also require
Microsoft.VisualStudio.Component.VC.Tools.x86.x64 so only installs
with the C++ toolset match; the broken BuildTools is excluded and
2026\Community is selected. CI VM BuildTools has the C++ workload so
it still matches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:00:10 +02:00
Simone b052497701 fix(build): pass -prerelease to vswhere (find VS2026)
VS2026 ships on the prerelease channel; vswhere omits it unless
-prerelease is given. Without it _find_msbuild_via_vswhere returned
nothing, so the path fallback picked an incomplete
(x86)\Microsoft Visual Studio\18\BuildTools install (MSBuild present
but no VC v180 targets) -> MSB4019 on hosts that have VS2026 IDE but
not BuildTools. -prerelease is harmless on the CI VM (BuildTools is
found regardless).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 21:57:13 +02:00
Simone ea71e0d8b2 perf(linux): full make -j per parallel config (no //nconfigs split)
Splitting make -j across configs (jobs // nconfigs) made each parallel
config single-threaded on a 4-vCPU VM, so 3 concurrent serial builds
were as slow as the old sequential path (~7min). Windows (MSBuild)
runs the 3 configs in parallel each using all cores and finishes in
~2min on the same vCPU/RAM. Mirror that: give every parallel config
the full make -j; the scheduler absorbs the mild oversubscription.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 21:26:23 +02:00
Simone c6e74909b4 perf(linux): build configs in parallel (default)
Linux built the 3 configs sequentially (~3x a single config); Windows
already parallelizes. Run them concurrently via ThreadPoolExecutor,
splitting make -j across configs to avoid CPU oversubscription.
Symlink setup serialized with a lock. --no-parallel restores
sequential. Brings Linux wall time close to Windows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 21:07:02 +02:00
Simone a9643c78e7 feat(build): add --dist to all remaining legacy Windows build scripts
Same --dist redirect (output to <repo>/dist/<config> instead of
plugins/<config>) applied to every per-version Windows script so the
flag works regardless of --7zip-version/--toolset the dispatcher
selects. Mirrors build_plugin_2601_vs2026.py + the Linux path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 20:35:47 +02:00
Simone 57133b71ee feat(build): add --dist to redirect output to dist/ instead of plugins/
When --dist is passed (forwarded by build_plugin.py to the per-version
script), built nsis7z.dll files are copied to <repo>/dist/<config>
instead of <repo>/plugins/<config>. Lets CI collect a clean dist/
tree without touching the committed plugins/ dir. Windows
(2601_vs2026) and Linux build paths both supported.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 20:33:37 +02:00
Simone 00ddcbd9ff ci: remove [skip ci] from dist pushback commit message 2026-05-05 00:39:40 +02:00
github-actions[bot] eb29dfe1f9 chore: update dist/ DLLs for v2.3.1 [skip ci] 2026-05-04 22:38:30 +00:00
Simone a731bedad0 fix: handle empty 204 response body in Gitea API calls 2026-05-05 00:34:33 +02:00
Simone 1aa358a78f fix: correct Gitea release asset DELETE endpoint path 2026-05-05 00:28:27 +02:00
Simone 505c9a609f ci: push back dist/ DLLs to Gitea after release build 2026-05-05 00:21:40 +02:00
Simone bfd4cff9ff ci: append empty line and -- to GitHub release body 2026-05-05 00:17:32 +02:00
Simone 002b7b4d54 ci: remove Gitea notice footer from GitHub release body 2026-05-05 00:16:37 +02:00
12 changed files with 182 additions and 20 deletions
+25 -8
View File
@@ -139,11 +139,8 @@ jobs:
/^## / && index($0, ver) { found=1; next }
found && /^## / { exit }
found { print }
' CHANGELOG.md > release_body.md
# Append Gitea notice
echo "" >> release_body.md
echo "---" >> release_body.md
echo "_Repository primario: vedi link Gitea nel README. Il mirror GitHub è generato automaticamente._" >> release_body.md
' CHANGELOG.md > release_body.md echo "" >> release_body.md
echo "--" >> release_body.md
- name: Package plugins ZIP
id: pkg
@@ -211,7 +208,8 @@ jobs:
},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
raw = resp.read()
return json.loads(raw) if raw else {}
def upload_asset(rid, path, auth_scheme):
p = Path(path)
@@ -234,7 +232,8 @@ jobs:
},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
raw = resp.read()
return json.loads(raw) if raw else {}
auth_scheme = None
last_error = None
@@ -291,7 +290,7 @@ jobs:
upload_names = {zip_name, "SHA256SUMS.txt"}
for asset in existing:
if asset["name"] in upload_names:
request("DELETE", f"/repos/{repo}/releases/assets/{asset['id']}", auth_scheme=auth_scheme)
request("DELETE", f"/repos/{repo}/releases/{rid}/assets/{asset['id']}", auth_scheme=auth_scheme)
print(f"Deleted existing asset: {asset['name']}")
# Carica ZIP e SHA256SUMS
@@ -303,3 +302,21 @@ jobs:
print(f"WARNING: {f} not found, skipping upload")
PY
- name: Push back dist/ DLLs to Gitea
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
tag="${{ steps.tag.outputs.tag }}"
# Copia le DLL compilate in dist/
for dir in amd64-unicode x86-ansi x86-unicode; do
mkdir -p "dist/$dir"
cp "dll-bundle/$dir/nsis7z.dll" "dist/$dir/nsis7z.dll"
done
# Configura git con le credenziali Gitea
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git remote add gitea "https://x-token:${GITEA_TOKEN}@gitea.emulab.it/Simone/nsis-plugin-ns7zip.git"
git add dist/
git diff --cached --quiet || git commit -m "chore: update dist/ DLLs for ${tag}"
git push gitea HEAD:main
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+7
View File
@@ -141,7 +141,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
str(_VSWHERE_PATH),
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -747,10 +749,15 @@ Examples:
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
)
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Get project paths early for list-project
project_dir, project_file, plugins_dir = get_project_paths()
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
# List project configurations
if args.list_project:
+7
View File
@@ -150,7 +150,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
'-products', '*',
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -763,10 +765,15 @@ Examples:
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
)
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Get project paths early for list-project
project_dir, project_file, plugins_dir = get_project_paths()
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
# List project configurations
if args.list_project:
+7
View File
@@ -150,7 +150,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
'-products', '*',
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -766,6 +768,8 @@ Examples:
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
)
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Find MSBuild early (needed for project path selection)
@@ -777,6 +781,9 @@ Examples:
# Get project paths (project file depends on toolset)
project_dir, project_file, plugins_dir = get_project_paths(platform_toolset)
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
# List project configurations
if args.list_project:
+8
View File
@@ -150,7 +150,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
'-products', '*',
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -630,6 +632,9 @@ Examples:
help='Disable additional build optimizations')
parser.add_argument('--vs-version', choices=['auto', '2026', '2022'], default='auto',
help='Visual Studio version to use (default: auto)')
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead '
'of <repo>/plugins/<config>')
args = parser.parse_args()
@@ -642,6 +647,9 @@ Examples:
# Get project paths
project_dir, project_file, plugins_dir = get_project_paths(platform_toolset)
# --dist: redirect the output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
if args.list_project:
if not project_file.exists():
+7
View File
@@ -146,7 +146,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
str(_VSWHERE_PATH),
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -747,10 +749,15 @@ Examples:
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
)
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Get project paths early for list-project
project_dir, project_file, plugins_dir = get_project_paths()
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
# List project configurations
if args.list_project:
+7
View File
@@ -150,7 +150,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
'-products', '*',
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -763,10 +765,15 @@ Examples:
help='Visual Studio version to use (default: auto - tries 2026 then 2022)'
)
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Get project paths early for list-project
project_dir, project_file, plugins_dir = get_project_paths()
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
# List project configurations
if args.list_project:
+7
View File
@@ -150,7 +150,9 @@ def _find_msbuild_via_vswhere(vs_version: str) -> 'Optional[Tuple[Path, str, str
'-products', '*',
'-version', _VS_VERSION_RANGE[ver],
'-latest',
'-prerelease',
'-requires', 'Microsoft.Component.MSBuild',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-find', r'MSBuild\**\Bin\MSBuild.exe',
],
capture_output=True, text=True, timeout=15,
@@ -631,6 +633,8 @@ Examples:
parser.add_argument('--vs-version', choices=['auto', '2026', '2022'], default='auto',
help='Visual Studio version to use (default: auto)')
parser.add_argument('--dist', action='store_true',
help='Copy built DLLs to <repo>/dist/<config> instead of <repo>/plugins/<config>')
args = parser.parse_args()
# Find MSBuild
@@ -642,6 +646,9 @@ Examples:
# Get project paths
project_dir, project_file, plugins_dir = get_project_paths(platform_toolset)
# --dist: redirect output root from <repo>/plugins to <repo>/dist.
if args.dist:
plugins_dir = plugins_dir.parent / 'dist'
if args.list_project:
if not project_file.exists():
+99 -4
View File
@@ -15,8 +15,15 @@ import os
import shutil
import subprocess
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
# Symlink creation is shared across configs; serialize it so parallel
# per-config builds don't race recreating the bundle symlinks.
_SYMLINK_LOCK = threading.Lock()
ROOT = Path(__file__).resolve().parents[2]
OVERLAY = ROOT / "tools" / "linux" / "overlay"
@@ -151,13 +158,16 @@ def _build_one(
jobs: int,
clean: bool,
cleanup_artifacts: bool,
dist: bool = False,
) -> int:
layout = VERSION_LAYOUT[zip_version]
vendor_7zip = layout["vendor_7zip"]
bundle_dir = layout["bundle_dir"]
# Symlinks are not committed to git; recreate them on Linux if missing.
# Serialized: concurrent per-config builds would race here.
if zip_version in VERSION_LAYOUT:
with _SYMLINK_LOCK:
_ensure_bundle_symlinks(zip_version, bundle_dir, vendor_7zip)
cfg = CONFIGS[cfg_name]
@@ -243,7 +253,10 @@ def _build_one(
print(f"ERROR: expected artifact not found: {built}", file=sys.stderr)
return 1
dest = ROOT / cfg["dest"]
rel_dest = cfg["dest"]
if dist and rel_dest.startswith("plugins/"):
rel_dest = "dist/" + rel_dest[len("plugins/"):]
dest = ROOT / rel_dest
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(built, dest)
@@ -257,6 +270,26 @@ def _build_one(
return 0
def _print_timing(
order: list[str],
codes: dict[str, int],
durations: dict[str, float],
wall: float,
) -> None:
"""Per-config + total wall-clock timing summary."""
print("\n" + "=" * 50)
print("[linux] Build timing")
print("-" * 50)
for cfg in order:
status = "OK " if codes.get(cfg) == 0 else "FAIL"
secs = durations.get(cfg)
secs_s = f"{secs:6.1f}s" if secs is not None else " N/A"
print(f" {status} {cfg:<13} {secs_s}")
print("-" * 50)
print(f" TOTAL wall clock {wall:6.1f}s")
print("=" * 50)
def main() -> int:
parser = argparse.ArgumentParser(description="Linux build path for nsis7z")
parser.add_argument(
@@ -303,6 +336,24 @@ def main() -> int:
help="Keep per-config build artifact directories (_o_*)",
)
parser.add_argument("--verbose", action="store_true", help="Verbose output")
parser.add_argument(
"--dist",
action="store_true",
help="Copy built DLLs to <repo>/dist/<config> instead of "
"<repo>/plugins/<config>",
)
parser.add_argument(
"--parallel",
action="store_true",
default=True,
help="Build configs concurrently (default: True)",
)
parser.add_argument(
"--no-parallel",
action="store_false",
dest="parallel",
help="Build configs one at a time",
)
args = parser.parse_args()
_require_tool("make")
@@ -319,17 +370,61 @@ def main() -> int:
wanted = list(CONFIGS.keys()) if "all" in args.configs else args.configs
if args.parallel and len(wanted) > 1:
# Each config gets the FULL make -j (no //nconfigs split). The
# configs are independent; mild CPU oversubscription is handled
# by the scheduler and is still far faster than serial — this is
# exactly what the Windows (MSBuild) parallel build does, which
# finishes in ~2min on the same vCPU/RAM. Splitting -j across
# configs neutered intra-config parallelism (on a 4-vCPU VM it
# became make -j1) and gave no speedup.
per_jobs = jobs
print(
f"[linux] Building {len(wanted)} configs in parallel "
f"(make -j{per_jobs} each)"
)
codes: dict[str, int] = {}
durations: dict[str, float] = {}
def _timed(cfg: str, jb: int) -> None:
t0 = time.perf_counter()
codes[cfg] = _build_one(
args.zip_version, cfg, args.verbose, jb,
args.clean, args.cleanup_artifacts, args.dist,
)
durations[cfg] = time.perf_counter() - t0
wall0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=len(wanted)) as ex:
futs = [ex.submit(_timed, c, per_jobs) for c in wanted]
for fut in futs:
fut.result()
wall = time.perf_counter() - wall0
_print_timing(wanted, codes, durations, wall)
for name in wanted:
if codes[name] != 0:
return codes[name]
else:
codes = {}
durations = {}
wall0 = time.perf_counter()
for cfg_name in wanted:
code = _build_one(
t0 = time.perf_counter()
codes[cfg_name] = _build_one(
args.zip_version,
cfg_name,
args.verbose,
jobs,
args.clean,
args.cleanup_artifacts,
args.dist,
)
if code != 0:
return code
durations[cfg_name] = time.perf_counter() - t0
if codes[cfg_name] != 0:
_print_timing(wanted, codes, durations,
time.perf_counter() - wall0)
return codes[cfg_name]
_print_timing(wanted, codes, durations, time.perf_counter() - wall0)
print("[linux] Build completed")
return 0