2023-01-26 08:13:07 +08:00
|
|
|
#! /usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# This file is part of npbackup
|
|
|
|
|
2023-03-26 21:36:17 +08:00
|
|
|
__intname__ = "npbackup.compile"
|
2023-01-26 08:13:07 +08:00
|
|
|
__author__ = "Orsiris de Jong"
|
|
|
|
__copyright__ = "Copyright (C) 2023 NetInvent"
|
|
|
|
__license__ = "GPL-3.0-only"
|
2023-03-28 20:49:09 +08:00
|
|
|
__build__ = "2023032801"
|
2023-03-28 01:23:17 +08:00
|
|
|
__version__ = "1.7.0"
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
2023-02-01 04:03:32 +08:00
|
|
|
import argparse
|
2023-02-01 08:28:25 +08:00
|
|
|
import atexit
|
2023-01-26 08:13:07 +08:00
|
|
|
from command_runner import command_runner
|
2023-01-29 01:07:32 +08:00
|
|
|
|
2023-02-01 08:51:15 +08:00
|
|
|
AUDIENCES = ["public", "private"]
|
2023-02-01 04:03:32 +08:00
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
# Insert parent dir as path se we get to use npbackup as package
|
|
|
|
sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
|
2023-03-26 21:34:22 +08:00
|
|
|
|
2023-01-27 18:30:06 +08:00
|
|
|
from npbackup.customization import (
|
2023-01-26 08:26:08 +08:00
|
|
|
COMPANY_NAME,
|
|
|
|
TRADEMARKS,
|
|
|
|
PRODUCT_NAME,
|
|
|
|
FILE_DESCRIPTION,
|
|
|
|
COPYRIGHT,
|
|
|
|
LICENSE_FILE,
|
|
|
|
)
|
2023-01-27 18:34:05 +08:00
|
|
|
from npbackup.core.restic_source_binary import get_restic_internal_binary
|
|
|
|
from npbackup.path_helper import BASEDIR
|
2023-02-01 04:03:32 +08:00
|
|
|
import glob
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
del sys.path[0]
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-02-01 08:51:15 +08:00
|
|
|
|
2023-03-26 21:34:22 +08:00
|
|
|
def _read_file(filename):
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
if sys.version_info[0] < 3:
|
|
|
|
# With python 2.7, open has no encoding parameter, resulting in TypeError
|
|
|
|
# Fix with io.open (slow but works)
|
|
|
|
from io import open as io_open
|
|
|
|
|
|
|
|
try:
|
|
|
|
with io_open(
|
|
|
|
os.path.join(here, filename), "r", encoding="utf-8"
|
|
|
|
) as file_handle:
|
|
|
|
return file_handle.read()
|
|
|
|
except IOError:
|
|
|
|
# Ugly fix for missing requirements.txt file when installing via pip under Python 2
|
|
|
|
return ""
|
|
|
|
else:
|
|
|
|
with open(os.path.join(here, filename), "r", encoding="utf-8") as file_handle:
|
|
|
|
return file_handle.read()
|
|
|
|
|
|
|
|
def get_metadata(package_file):
|
|
|
|
"""
|
|
|
|
Read metadata from package file
|
|
|
|
"""
|
|
|
|
|
|
|
|
_metadata = {}
|
|
|
|
|
|
|
|
for line in _read_file(package_file).splitlines():
|
|
|
|
if line.startswith("__version__") or line.startswith("__description__"):
|
|
|
|
delim = "="
|
|
|
|
_metadata[line.split(delim)[0].strip().strip("__")] = (
|
|
|
|
line.split(delim)[1].strip().strip("'\"")
|
|
|
|
)
|
|
|
|
return _metadata
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
def check_private_build(audience):
|
2023-03-27 01:05:20 +08:00
|
|
|
private = None
|
2023-01-26 08:13:07 +08:00
|
|
|
try:
|
2023-03-28 01:23:17 +08:00
|
|
|
import PRIVATE._private_secret_keys
|
2023-03-28 01:26:00 +08:00
|
|
|
print("INFO: Building with private secret key")
|
2023-01-26 08:13:07 +08:00
|
|
|
private = True
|
|
|
|
except ImportError:
|
|
|
|
try:
|
2023-01-29 01:07:32 +08:00
|
|
|
import npbackup.secret_keys
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-03-28 01:27:00 +08:00
|
|
|
print("INFO: Building with default secret key")
|
2023-03-27 01:05:20 +08:00
|
|
|
private = False
|
2023-01-26 08:13:07 +08:00
|
|
|
except ImportError:
|
2023-03-28 01:27:00 +08:00
|
|
|
print("ERROR: Cannot find secret keys")
|
2023-01-26 08:13:07 +08:00
|
|
|
sys.exit()
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-03-28 20:45:32 +08:00
|
|
|
# Drop private files if exist in memory
|
|
|
|
try:
|
|
|
|
del PRIVATE._private_secret_keys
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
dist_conf_file_path = get_conf_dist_file(audience)
|
2023-03-27 01:05:20 +08:00
|
|
|
if dist_conf_file_path and "_private" in dist_conf_file_path:
|
2023-03-28 01:26:00 +08:00
|
|
|
print("INFO: Building with a private conf.dist file")
|
2023-03-27 01:05:20 +08:00
|
|
|
if audience != "private":
|
|
|
|
print("ERROR: public build uses private conf.dist file")
|
|
|
|
sys.exit(6)
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-01-26 08:13:07 +08:00
|
|
|
return private
|
|
|
|
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
def move_audience_files(audience):
|
2023-03-28 01:26:00 +08:00
|
|
|
for dir in [os.path.join(BASEDIR, os.pardir, "PRIVATE"), BASEDIR]:
|
2023-02-01 08:51:15 +08:00
|
|
|
if audience == "private":
|
|
|
|
possible_non_used_path = "_NOUSE_private_"
|
|
|
|
guessed_files = glob.glob(
|
|
|
|
os.path.join(dir, "{}*".format(possible_non_used_path))
|
|
|
|
)
|
2023-02-01 04:03:32 +08:00
|
|
|
for file in guessed_files:
|
2023-03-26 21:34:22 +08:00
|
|
|
new_file = file.replace(possible_non_used_path, "_private_")
|
|
|
|
os.rename(file, new_file)
|
2023-02-01 08:51:15 +08:00
|
|
|
elif audience == "public":
|
|
|
|
possible_non_used_path = "_private_"
|
|
|
|
guessed_files = glob.glob(
|
|
|
|
os.path.join(dir, "{}*".format(possible_non_used_path))
|
|
|
|
)
|
2023-02-01 04:03:32 +08:00
|
|
|
for file in guessed_files:
|
2023-03-26 21:34:22 +08:00
|
|
|
new_file = file.replace(
|
2023-02-01 08:51:15 +08:00
|
|
|
possible_non_used_path,
|
|
|
|
"_NOUSE{}".format(possible_non_used_path),
|
2023-03-26 21:34:22 +08:00
|
|
|
)
|
|
|
|
os.rename(
|
|
|
|
file, new_file
|
2023-02-01 08:51:15 +08:00
|
|
|
)
|
2023-03-26 21:34:22 +08:00
|
|
|
else:
|
|
|
|
raise "Bogus audience"
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
|
|
|
|
def get_conf_dist_file(audience):
|
2023-02-01 08:51:15 +08:00
|
|
|
if audience == "private":
|
2023-03-28 01:23:17 +08:00
|
|
|
dist_conf_file_path = os.path.join(BASEDIR, os.pardir, "PRIVATE", "_private_npbackup.conf.dist")
|
2023-02-01 04:03:32 +08:00
|
|
|
else:
|
2023-03-28 01:23:17 +08:00
|
|
|
dist_conf_file_path = os.path.join(BASEDIR, os.pardir, "examples", "npbackup.conf.dist")
|
2023-03-27 01:05:20 +08:00
|
|
|
if not os.path.isfile(dist_conf_file_path):
|
|
|
|
print("DIST CONF FILE NOT FOUND: {}".format(dist_conf_file_path))
|
|
|
|
return None
|
2023-02-01 08:51:15 +08:00
|
|
|
return dist_conf_file_path
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
def have_nuitka_commercial():
|
|
|
|
try:
|
|
|
|
import nuitka.plugins.commercial
|
|
|
|
print("Running with nuitka commercial")
|
|
|
|
return True
|
|
|
|
except ImportError:
|
|
|
|
print("Running with nuitka open source")
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
def compile(arch, audience):
|
2023-01-29 01:07:32 +08:00
|
|
|
if os.name == "nt":
|
|
|
|
program_executable = "npbackup.exe"
|
|
|
|
restic_executable = "restic.exe"
|
2023-02-01 04:03:32 +08:00
|
|
|
platform = "windows"
|
2023-01-29 01:07:32 +08:00
|
|
|
else:
|
|
|
|
program_executable = "npbackup"
|
|
|
|
restic_executable = "restic"
|
2023-02-01 04:03:32 +08:00
|
|
|
platform = "linux"
|
2023-01-29 01:07:32 +08:00
|
|
|
|
2023-02-01 08:51:15 +08:00
|
|
|
PACKAGE_DIR = "npbackup"
|
2023-01-29 01:07:32 +08:00
|
|
|
|
2023-02-01 06:39:09 +08:00
|
|
|
BUILDS_DIR = os.path.abspath(os.path.join(BASEDIR, os.pardir, "BUILDS"))
|
|
|
|
OUTPUT_DIR = os.path.join(BUILDS_DIR, audience, platform, arch)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
if not os.path.isdir(OUTPUT_DIR):
|
|
|
|
os.makedirs(OUTPUT_DIR)
|
|
|
|
|
2023-01-26 08:26:08 +08:00
|
|
|
PYTHON_EXECUTABLE = sys.executable
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
# npbackup compilation
|
|
|
|
# Strip possible version suffixes '-dev'
|
2023-01-26 08:26:08 +08:00
|
|
|
_npbackup_version = npbackup_version.split("-")[0]
|
|
|
|
PRODUCT_VERSION = _npbackup_version + ".0"
|
|
|
|
FILE_VERSION = _npbackup_version + ".0"
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-27 18:42:05 +08:00
|
|
|
file_description = "{} P{}-{}{}".format(
|
2023-02-01 08:51:15 +08:00
|
|
|
FILE_DESCRIPTION,
|
|
|
|
sys.version_info[1],
|
|
|
|
arch,
|
|
|
|
"priv" if audience == "private" else "",
|
2023-01-27 18:42:05 +08:00
|
|
|
)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
restic_source_file = get_restic_internal_binary(arch)
|
|
|
|
if not restic_source_file:
|
2023-01-26 08:26:08 +08:00
|
|
|
print("Cannot find restic source file.")
|
2023-03-26 03:14:28 +08:00
|
|
|
return False
|
2023-03-27 21:44:43 +08:00
|
|
|
else:
|
|
|
|
os.chmod(restic_source_file, 0o775)
|
2023-01-29 01:07:32 +08:00
|
|
|
restic_dest_file = os.path.join(PACKAGE_DIR, restic_executable)
|
|
|
|
|
2023-01-26 08:26:08 +08:00
|
|
|
translations_dir = "translations"
|
2023-01-29 01:07:32 +08:00
|
|
|
translations_dir_source = os.path.join(BASEDIR, translations_dir)
|
|
|
|
translations_dir_dest = os.path.join(PACKAGE_DIR, translations_dir)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
license_dest_file = os.path.join(PACKAGE_DIR, os.path.basename(LICENSE_FILE))
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-02-01 08:51:15 +08:00
|
|
|
icon_file = os.path.join(PACKAGE_DIR, "npbackup_icon.ico")
|
2023-01-29 01:07:32 +08:00
|
|
|
|
|
|
|
# Installer specific files, no need for a npbackup package directory here
|
2023-01-29 20:36:31 +08:00
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
program_executable_path = os.path.join(OUTPUT_DIR, program_executable)
|
2023-01-29 20:36:31 +08:00
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
dist_conf_file_source = get_conf_dist_file(audience)
|
2023-03-27 01:05:20 +08:00
|
|
|
if not dist_conf_file_source:
|
|
|
|
print("Stopped {} compilation".format(audience))
|
|
|
|
return
|
2023-02-01 08:51:15 +08:00
|
|
|
dist_conf_file_dest = os.path.basename(
|
|
|
|
dist_conf_file_source.replace("_private_", "")
|
|
|
|
)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
excludes_dir = "excludes"
|
2023-01-29 01:40:16 +08:00
|
|
|
excludes_dir_source = os.path.join(BASEDIR, os.pardir, excludes_dir)
|
2023-01-29 01:07:32 +08:00
|
|
|
excludes_dir_dest = excludes_dir
|
2023-01-29 20:36:31 +08:00
|
|
|
|
2023-02-01 08:51:15 +08:00
|
|
|
NUITKA_OPTIONS = "--enable-plugin=data-hiding" if have_nuitka_commercial() else ""
|
2023-01-29 20:36:31 +08:00
|
|
|
|
2023-01-26 08:26:08 +08:00
|
|
|
EXE_OPTIONS = '--company-name="{}" --product-name="{}" --file-version="{}" --product-version="{}" --copyright="{}" --file-description="{}" --trademarks="{}"'.format(
|
|
|
|
COMPANY_NAME,
|
|
|
|
PRODUCT_NAME,
|
|
|
|
FILE_VERSION,
|
|
|
|
PRODUCT_VERSION,
|
|
|
|
COPYRIGHT,
|
|
|
|
file_description,
|
|
|
|
TRADEMARKS,
|
|
|
|
)
|
2023-01-29 01:07:32 +08:00
|
|
|
|
|
|
|
CMD = '{} -m nuitka --python-flag=no_docstrings --python-flag=-O {} {} --onefile --plugin-enable=tk-inter --include-data-dir="{}"="{}" --include-data-file="{}"="{}" --include-data-file="{}"="{}" --windows-icon-from-ico="{}" --output-dir="{}" bin/npbackup'.format(
|
2023-01-26 08:26:08 +08:00
|
|
|
PYTHON_EXECUTABLE,
|
2023-01-29 01:07:32 +08:00
|
|
|
NUITKA_OPTIONS,
|
|
|
|
EXE_OPTIONS,
|
|
|
|
translations_dir_source,
|
|
|
|
translations_dir_dest,
|
2023-01-26 08:26:08 +08:00
|
|
|
LICENSE_FILE,
|
2023-01-29 01:07:32 +08:00
|
|
|
license_dest_file,
|
2023-01-26 08:26:08 +08:00
|
|
|
restic_source_file,
|
2023-01-29 01:07:32 +08:00
|
|
|
restic_dest_file,
|
|
|
|
icon_file,
|
2023-02-01 04:03:32 +08:00
|
|
|
OUTPUT_DIR,
|
2023-01-26 08:26:08 +08:00
|
|
|
)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
|
|
|
print(CMD)
|
|
|
|
errors = False
|
|
|
|
exit_code, output = command_runner(CMD, timeout=0, live_output=True)
|
|
|
|
if exit_code != 0:
|
|
|
|
errors = True
|
|
|
|
|
2023-03-28 01:23:17 +08:00
|
|
|
# windows only installer compilation
|
2023-03-21 23:26:32 +08:00
|
|
|
if os.name == "nt":
|
|
|
|
_installer_version = installer_version.split("-")[0]
|
|
|
|
PRODUCT_VERSION = _installer_version + ".0"
|
|
|
|
FILE_VERSION = _installer_version + ".0"
|
|
|
|
EXE_OPTIONS = '--company-name="{}" --product-name="{}" --file-version="{}" --product-version="{}" --copyright="{}" --file-description="{}" --trademarks="{}"'.format(
|
|
|
|
COMPANY_NAME,
|
|
|
|
PRODUCT_NAME,
|
|
|
|
FILE_VERSION,
|
|
|
|
PRODUCT_VERSION,
|
|
|
|
COPYRIGHT,
|
|
|
|
file_description,
|
|
|
|
TRADEMARKS,
|
|
|
|
)
|
|
|
|
CMD = '{} -m nuitka --python-flag=no_docstrings --python-flag=-O {} {} --onefile --plugin-enable=tk-inter --include-data-file="{}"="{}" --include-data-file="{}"="{}" --include-data-dir="{}"="{}" --windows-icon-from-ico="{}" --windows-uac-admin --output-dir="{}" bin/NPBackupInstaller.py'.format(
|
|
|
|
PYTHON_EXECUTABLE,
|
|
|
|
NUITKA_OPTIONS,
|
|
|
|
EXE_OPTIONS,
|
|
|
|
program_executable_path,
|
|
|
|
program_executable,
|
|
|
|
dist_conf_file_source,
|
|
|
|
dist_conf_file_dest,
|
|
|
|
excludes_dir_source,
|
|
|
|
excludes_dir_dest,
|
|
|
|
icon_file,
|
|
|
|
OUTPUT_DIR,
|
|
|
|
)
|
|
|
|
|
|
|
|
print(CMD)
|
|
|
|
exit_code, output = command_runner(CMD, timeout=0, live_output=True)
|
|
|
|
if exit_code != 0:
|
|
|
|
errors = True
|
|
|
|
else:
|
|
|
|
## Create version file
|
|
|
|
with open(os.path.join(BUILDS_DIR, audience, "VERSION"), "w") as fh:
|
|
|
|
fh.write(npbackup_version)
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-29 01:07:32 +08:00
|
|
|
print("COMPILE ERRORS", errors)
|
2023-03-27 01:05:20 +08:00
|
|
|
return not errors
|
2023-01-26 08:13:07 +08:00
|
|
|
|
2023-01-26 08:26:08 +08:00
|
|
|
|
2023-02-01 04:03:32 +08:00
|
|
|
class AudienceAction(argparse.Action):
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
2023-02-01 08:51:15 +08:00
|
|
|
if values not in AUDIENCES + ["all"]:
|
2023-02-01 04:03:32 +08:00
|
|
|
print("Got value:", values)
|
|
|
|
raise argparse.ArgumentError(self, "Not a valid audience")
|
2023-02-01 08:51:15 +08:00
|
|
|
setattr(namespace, self.dest, values)
|
2023-02-01 04:03:32 +08:00
|
|
|
|
|
|
|
|
2023-01-26 08:13:07 +08:00
|
|
|
if __name__ == "__main__":
|
2023-02-01 04:03:32 +08:00
|
|
|
parser = argparse.ArgumentParser(
|
2023-02-01 08:51:15 +08:00
|
|
|
prog="npbackup compile.py", description="Compiler script for NPBackup"
|
2023-02-01 04:03:32 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
"--audience",
|
|
|
|
type=str,
|
|
|
|
dest="audience",
|
2023-02-01 08:51:15 +08:00
|
|
|
default="private",
|
2023-02-01 04:03:32 +08:00
|
|
|
required=False,
|
|
|
|
help="Target audience, private or public",
|
|
|
|
)
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
2023-02-01 08:28:25 +08:00
|
|
|
|
|
|
|
# Make sure we get out dev environment back when compilation ends / fails
|
2023-02-01 08:51:15 +08:00
|
|
|
atexit.register(
|
|
|
|
move_audience_files,
|
|
|
|
"private",
|
|
|
|
)
|
2023-02-01 08:28:25 +08:00
|
|
|
try:
|
2023-03-28 00:18:57 +08:00
|
|
|
errors = False
|
2023-02-01 08:51:15 +08:00
|
|
|
if args.audience.lower() == "all":
|
2023-02-01 08:28:25 +08:00
|
|
|
audiences = AUDIENCES
|
|
|
|
else:
|
|
|
|
audiences = [args.audience]
|
2023-02-01 08:51:15 +08:00
|
|
|
|
2023-02-01 08:28:25 +08:00
|
|
|
for audience in audiences:
|
|
|
|
move_audience_files(audience)
|
2023-03-26 21:34:22 +08:00
|
|
|
npbackup_version = get_metadata(os.path.join(BASEDIR, "__main__.py"))["version"]
|
|
|
|
installer_version = get_metadata(os.path.join(BASEDIR, os.pardir, "bin", "NPBackupInstaller.py"))["version"]
|
|
|
|
|
2023-03-26 03:14:28 +08:00
|
|
|
private_build = check_private_build(audience)
|
|
|
|
if private_build and audience != "private":
|
2023-03-28 01:23:17 +08:00
|
|
|
print("ERROR: Requested public build but private data available")
|
|
|
|
errors = True
|
|
|
|
continue
|
|
|
|
elif not private_build and audience != "public":
|
2023-03-26 03:14:28 +08:00
|
|
|
print("ERROR: Requested private build but no private data available")
|
2023-03-28 01:23:17 +08:00
|
|
|
errors = True
|
2023-03-26 03:14:28 +08:00
|
|
|
continue
|
2023-03-28 00:18:57 +08:00
|
|
|
python_arch = "x64" if sys.maxsize > 2**32 else "x86"
|
|
|
|
result = compile(arch=python_arch, audience=audience)
|
|
|
|
build_type = 'private' if private_build else 'public'
|
2023-03-27 01:05:20 +08:00
|
|
|
if result:
|
2023-03-28 00:18:57 +08:00
|
|
|
print("SUCCESS: MADE {} build for audience {}".format(build_type, audience))
|
|
|
|
else:
|
|
|
|
print("ERROR: Failed making {} build for audience {}".format(build_type, audience))
|
|
|
|
errors = True
|
|
|
|
if errors:
|
|
|
|
print("ERRORS IN BUILD PROCESS")
|
|
|
|
else:
|
|
|
|
print("SUCCESS BUILDING")
|
2023-02-01 08:28:25 +08:00
|
|
|
except Exception:
|
|
|
|
print("COMPILATION FAILED")
|
2023-03-26 21:34:22 +08:00
|
|
|
raise
|