#!/usr/bin/env perl # Copyright (c) 2025, R. W. Rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # make_release # Builds release tarball and sha256 file from one svn export of a tag, ^/testing or ^/stable, # or from the current uncommitted working copy (--working-copy) for testing on beta machines. # The tarball holds only unix/ (laid out as the install directory); the Windows client (windows/) is # packaged separately as a zip and sha256 file. Everything else at the repository root stays out of both. # # Revision History: # 2026-10-13 Copilot v1.0.0 r? initial v3.8.0 release builder # 2026-10-13 Copilot v1.0.1 apply stage-3 exclusion list and shared command execution # 2026-10-14 Claude v1.0.2 no longer drops old/ (the directory was removed from the repository) # 2026-10-16 Claude v1.1.0 source may be the branch name 'testing' or 'stable' as well as a version tag # 2026-10-17 Claude v1.2.0 --working-copy builds from this working copy, uncommitted and unversioned files included; # windows/ leaves the tarball and becomes sysinfo-client-windows.zip and .sha256 (bug 211) # 2026-10-18 Claude v1.3.0 repository split: the export is the whole repository, the tarball is built from unix/ and # the zip from windows/ (tests now live in t/windows at the root and are removed with t/) use strict; use warnings; BEGIN { use FindBin; use File::Spec; use lib File::Spec->catdir($FindBin::Bin, '../unix/installer'); } use File::Basename qw(basename dirname); use File::Path qw(make_path remove_tree); use Getopt::Long; use Cwd qw(abs_path); use Digest::SHA qw(sha256_hex); use version; our $VERSION = version->declare('v1.3.0'); use installer_shared qw( runCommandList ); my $help = 0; my $showVersion = 0; my $workingCopy = 0; my $workDir = '/tmp/sysinfo_release_work'; my $repoRoot = 'http://svn.dailydata.net/svn/camp_sysinfo_client_3'; sub printHelp { print basename($0) . " $VERSION\n"; print <<'END_HELP'; make_release [--workdir /tmp/path] [--repo-root URL] make_release --working-copy [--workdir /tmp/path] is exported from tags/; 'testing' and 'stable' are exported from the branch of that name. The export is the whole repository; the tarball is built from its unix/ directory and the Windows zip from windows/. --working-copy packages this working copy as it is now, uncommitted changes and new unversioned files included (.svn and .vscode are left out), and takes no tag. It is for trying changes on beta machines before they are committed; the result cannot be rebuilt later, so never publish it as a release. END_HELP } ####################################################### # # writeChecksum( $path ) # # Writes " " to $path.sha256 (the # sha256sum -c format) and returns the checksum path. # ####################################################### sub writeChecksum { my ($path) = @_; open my $fh, '<', $path or die "could not read $path\n"; binmode($fh); my $ctx = Digest::SHA->new(256); $ctx->addfile($fh); close($fh); my $sumPath = $path . '.sha256'; open my $out, '>', $sumPath or die "could not write checksum\n"; print {$out} $ctx->hexdigest . ' ' . basename($path) . "\n"; close($out); return $sumPath; } GetOptions( 'help|h' => \$help, 'version|v' => \$showVersion, 'working-copy|w' => \$workingCopy, 'workdir=s' => \$workDir, 'repo-root=s' => \$repoRoot, ) or die "Error parsing command line\n"; if ($help) { printHelp(); exit 0; } if ($showVersion) { print basename($0) . " $VERSION\n"; exit 0; } my $tag = shift @ARGV; if ($workingCopy) { die "--working-copy takes no version tag\n" if defined($tag) && $tag ne ''; } else { die "version tag, 'testing' or 'stable' required\n" unless defined($tag) && $tag ne ''; } my $exportDir = $workDir . '/repo'; my $clientDir = $workDir . '/sysinfo_client'; remove_tree($workDir) if -d $workDir; make_path($workDir); if ($workingCopy) { # svn export of a working copy skips unversioned files, and new files are normally added # to svn late, so copy the directory itself (through a tar file; no shell involved) my $wcRoot = abs_path($FindBin::Bin . '/..'); make_path($exportDir); my $copyTar = $workDir . '/working-copy.tar'; runCommandList(['tar', '-cf', $copyTar, '--exclude', '.svn', '--exclude', '.vscode', '--exclude', '.bugzilla*', '--exclude', '*~', '--exclude', './WorkingNotes', '--exclude', './reference', '--exclude', './server', '--exclude', './t', '-C', $wcRoot, '.'], 0) == 0 or die "working copy archive failed\n"; runCommandList(['tar', '-xf', $copyTar, '-C', $exportDir], 0) == 0 or die "working copy unpack failed\n"; unlink($copyTar); print "WARNING: built from the uncommitted working copy $wcRoot, not from a repository path\n"; } else { my $source = $repoRoot . ($tag =~ /^(?:testing|stable)$/ ? '/' : '/tags/') . $tag; runCommandList(['svn', 'export', $source, $exportDir], 0) == 0 or die "svn export failed\n"; } # the tarball is unix/ alone, same layout as the install directory; windows/ ships as its own zip die "export has no unix/ directory (repository not split yet?)\n" unless -d "$exportDir/unix"; rename("$exportDir/unix", $clientDir) or die "could not move unix directory: $!\n"; # the Windows client ships as a zip (single top directory, no tests), never in the tarball; # zipped with python3 because the workstation has no zip binary my @windowsOutputs; if (-d "$exportDir/windows") { my $winDir = $workDir . '/sysinfo_client_windows'; rename("$exportDir/windows", $winDir) or die "could not move windows directory: $!\n"; remove_tree("$winDir/t") if -d "$winDir/t"; my $zipPath = $workDir . '/sysinfo-client-windows.zip'; runCommandList(['python3', '-m', 'zipfile', '-c', $zipPath, $winDir], 0) == 0 or die "zip creation failed (python3 needed)\n"; push @windowsOutputs, $zipPath, writeChecksum($zipPath); } remove_tree($exportDir); opendir(my $edh, $clientDir) or die "could not read export directory\n"; my @topNames = grep { $_ ne '.' && $_ ne '..' } readdir($edh); closedir($edh); foreach my $name (@topNames) { my $path = "$clientDir/$name"; if ($name =~ /^test_.*\.pl$/ || $name =~ /^test_simple\./ || $name eq 'test.yaml' || $name =~ /\.tmp~$/ || $name eq 'sysinfo-client.yaml') { unlink($path) if -f $path; } } my $tarPath = $workDir . '/sysinfo-client.tgz'; runCommandList(['tar', '-czf', $tarPath, '-C', $workDir, 'sysinfo_client'], 0) == 0 or die "tar creation failed\n"; my $sumPath = writeChecksum($tarPath); print "Created $tarPath\n"; print "Created $sumPath\n"; print "Created $_\n" foreach @windowsOutputs; exit 0;