Skip to content

Instantly share code, notes, and snippets.

@tritium21
Last active May 29, 2020 09:28
Show Gist options
  • Select an option

  • Save tritium21/71acb9091845ce0f63ee049aae7b9172 to your computer and use it in GitHub Desktop.

Select an option

Save tritium21/71acb9091845ce0f63ee049aae7b9172 to your computer and use it in GitHub Desktop.
A tool to make a windows installer for nikola
python*.zip
dist/
import argparse
import pathlib
import platform
import shutil
import subprocess
import sys
import urllib.request
import zipfile
ROOT = pathlib.Path(__file__).resolve().parent
BATCH = """\
@echo off
"%~dp0\\nikola\\python.exe" -m nikola %*
"""
def get_version(app):
f = app / 'nikola' / '__init__.py'
version_line = next(l for l in f.read_text().splitlines() if l.startswith('__version__'))
return version_line.partition(' = ')[2].strip("'")
def fetch_embedded(dist):
version = platform.python_version()
arch = 'amd64' if platform.architecture()[0] == '64bit' else 'win32'
cache = ROOT / f'python-{version}-embed-{arch}.zip'
if not cache.is_file():
url = f'https://www.python.org/ftp/python/{version}/python-{version}-embed-{arch}.zip'
with urllib.request.urlopen(url) as s:
cache.write_bytes(s.read())
with zipfile.ZipFile(cache) as z:
z.extractall(path=dist, members=None, pwd=None)
def make_setup(app, iscc):
iss = ROOT / 'setup.iss'
subprocess.run([iscc, iss, f'/DMyAppVersion={get_version(app)}'])
def make(
dist=(ROOT / 'dist'),
iscc=r'c:\Program Files (x86)\Inno Setup 6\ISCC.exe',
setup=True,
):
dist = pathlib.Path(dist).resolve()
app = dist / 'nikola'
python = pathlib.Path(sys.executable).resolve()
iscc = pathlib.Path(iscc).resolve()
exe = dist / 'nikola.bat'
if dist.exists():
print(f"Removing {dist}", file=sys.stderr)
shutil.rmtree(dist)
fetch_embedded(app)
subprocess.run([python, '-m', 'pip', 'install', '--target', app, 'nikola[extras]'])
exe.write_text(BATCH)
for dir_ in dist.rglob('__pycache__/'):
if dir_.is_dir():
print(f"Removing {dir_}", file=sys.stderr)
shutil.rmtree(dir_)
bin_ = (app / 'bin')
if bin_.is_dir():
print(f"Removing {bin_}", file=sys.stderr)
shutil.rmtree(bin_)
if setup and iscc.exists():
make_setup(app, iscc)
def main(argv=sys.argv[1:]):
dist = ROOT / 'dist'
iscc = r'c:\Program Files (x86)\Inno Setup 6\ISCC.exe'
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument('--iscc', default=iscc, help='Path to Inno Setup iscc.exe')
parser.add_argument('--no-setup', action='store_false', dest='setup')
parser.add_argument('dist', default=dist, help='Output Directory', nargs='?')
args = parser.parse_args(argv)
make(**vars(args))
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Nikola"
; #define MyAppVersion "1.5"
#define MyAppPublisher "Nikola, a static site generator "
#define MyAppURL "https://getnikola.com/"
#define MyAppExeName "nikola.bat"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{1E6EC0CF-C94C-4C5B-A5D3-65C4AD69FDDC}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=dist
OutputBaseFilename=nikola-{#SetupSetting("AppVersion")}-setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
ChangesEnvironment=true
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
[Files]
Source: "dist\nikola.bat"; DestDir: "{app}"; Flags: ignoreversion
Source: "dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Tasks]
Name: envPath; Description: "Add to PATH variable"
[Code]
const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
procedure EnvAddPath(Path: string);
var
Paths: string;
begin
{ Retrieve current path (use empty string if entry not exists) }
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
then Paths := '';
{ Skip if string already found in path }
if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit;
{ App string to the end of the path variable }
Paths := Paths + ';'+ Path +';'
{ Overwrite (or create if missing) path environment variable }
if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths]))
else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths]));
end;
procedure EnvRemovePath(Path: string);
var
Paths: string;
P: Integer;
begin
{ Skip if registry entry not exists }
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
exit;
{ Skip if string not found in path }
P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
if P = 0 then exit;
{ Update path variable }
Delete(Paths, P - 1, Length(Path) + 1);
{ Overwrite path environment variable }
if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths]))
else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths]));
end;
/////////////////////////////////////////////////////////////////////
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
/////////////////////////////////////////////////////////////////////
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
/////////////////////////////////////////////////////////////////////
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString
// default return value
Result := 0;
// get the uninstall string of the old app
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
/////////////////////////////////////////////////////////////////////
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep = ssPostInstall) and IsTaskSelected('envPath')
then EnvAddPath(ExpandConstant('{app}'));
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall
then EnvRemovePath(ExpandConstant('{app}'));
end;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment