aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-10-02 18:47:24 +0200
committerdec05eba <dec05eba@protonmail.com>2020-07-06 07:39:33 +0200
commitcd98e64f462e650c9ec4cb673c2b77831396113b (patch)
treee66603d41e338b730b221660e0f5da5bbe7bb1d5
parent1ffa0d60b0f253506a4e1e335f98c71bba3b866d (diff)
Add bundle command to sibs package
Bundle command copies all dynamic library dependencies to one location and creates an archive that can be distributed. Currently only for linux. In testing phase...
-rwxr-xr-xscripts/package.py79
-rw-r--r--src/main.cpp146
2 files changed, 213 insertions, 12 deletions
diff --git a/scripts/package.py b/scripts/package.py
new file mode 100755
index 0000000..3de3448
--- /dev/null
+++ b/scripts/package.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+
+import subprocess
+import sys
+import os
+import shutil
+import re
+import stat
+import tarfile
+
+def get_executable_dynamic_libraries(filepath):
+ libs = []
+ process = subprocess.Popen(["ldd", filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = process.communicate()
+ if process.returncode != 0:
+ raise RuntimeError("Failed to execute ldd on executable %s, error:\n%s" % (filepath, stderr))
+ lines = stdout.splitlines()
+ for line in lines:
+ s = line.split()
+ if b"=>" in s:
+ if len(s) >= 3:
+ libs.append(os.path.realpath(s[2].decode("UTF-8")))
+ return libs
+
+def make_executable(filepath):
+ mode = os.stat(filepath).st_mode
+ os.chmod(filepath, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+
+def main():
+ if len(sys.argv) <= 2:
+ print("usage: %s executable_path destination_path" % sys.argv[0])
+ exit(1)
+
+ os.makedirs(sys.argv[2], exist_ok=True)
+ libs = get_executable_dynamic_libraries(sys.argv[1])
+
+ so_loader_pattern = re.compile("ld-[0-9.]+\\.so.*")
+ so_loader = None
+ for lib in libs:
+ lib_filename = os.path.basename(lib)
+ if so_loader_pattern.match(lib_filename):
+ if so_loader != None:
+ print("Unexpected error: found multiple so loaders, unable to recover")
+ exit(2)
+ so_loader = lib_filename
+
+ executable_filename = os.path.basename(sys.argv[1])
+ new_executable_path = os.path.join(sys.argv[2], executable_filename)
+ shutil.copyfile(sys.argv[1], new_executable_path)
+ make_executable(new_executable_path)
+
+ print("Patching executable")
+ process = subprocess.Popen(["patchelf", "--set-interpreter", so_loader, new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = process.communicate()
+ if process.returncode != 0:
+ print("Failed to execute patchelf --set-interpreter on executable %s, error: %s" % (new_executable_path, stderr))
+ exit(3)
+
+ process = subprocess.Popen(["patchelf", "--set-rpath", ".", new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = process.communicate()
+ if process.returncode != 0:
+ print("Failed to execute patchelf --set-rpath on executable %s, error: %s" % (new_executable_path, stderr))
+ exit(4)
+
+ package_name = new_executable_path + ".tar.gz"
+ print("Creating package %s" % os.path.basename(package_name))
+ with tarfile.open(package_name, "w:gz") as tar:
+ for lib in libs:
+ print("Adding shared library %s to tar" % lib)
+ tar.add(lib, arcname=os.path.basename(lib))
+ print("Adding executable %s to tar" % sys.argv[1])
+ tar.add(new_executable_path, arcname=os.path.basename(new_executable_path))
+
+ print("Removing temporary file %s" % new_executable_path)
+ os.remove(new_executable_path)
+ print("Package has been created at %s" % package_name)
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index eae90d0..6f54ba5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -174,19 +174,21 @@ static void usageInit()
printf(" --lang\t\t\tProject template language - Optional (default: c++)\n");
printf("Examples:\n");
printf(" sibs init . --exec\n");
- printf(" sibs init dirA/dirB --dynamic");
+ printf(" sibs init dirA/dirB --dynamic\n");
exit(1);
}
static void usagePackage()
{
- printf("Usage: sibs package [project_path]\n\n");
- printf("Create a redistributable package from a sibs project. Note: Redistributable packages can't use system packages to build\n\n");
+ printf("Usage: sibs package [project_path] <--static|--bundle>\n\n");
+ printf("Create a redistributable package from a sibs project. Note: Redistributable packages can't use system packages to build if packaging using --static\n\n");
printf("Options:\n");
printf(" project_path\t\tThe directory containiung a project.conf file - Optional (default: current directory)\n");
+ printf(" --static\t\tPackage project by building everything statically. Note: can't use system packages when using this option (meaning no pkg-config support)\n\n");
+ printf(" --bundle\t\tPackage project by copying all dynamic libraries into one location and creating an archive of all files. The executable is patched to use the dynamic libraries in the same directory. Note: if your project loads dynamic libraries at runtime (for example using dlopen) then you need to manually copy those libraries to the archive\n\n");
printf("Examples:\n");
- printf(" sibs package\n");
- printf(" sibs package dirA/dirB");
+ printf(" sibs package --static\n");
+ printf(" sibs package dirA/dirB --bundle\n");
exit(1);
}
@@ -927,14 +929,114 @@ static int initProject(int argc, const _tinydir_char_t **argv)
return 0;
}
+enum class PackagingType
+{
+ NONE,
+ STATIC,
+ BUNDLE
+};
+
+static const char* asString(PackagingType packagingType)
+{
+ switch(packagingType)
+ {
+ case PackagingType::STATIC: return "--static";
+ case PackagingType::BUNDLE: return "--bundle";
+ default: return "none";
+ }
+}
+
+static void validateSibsScriptDir(const FileString &sibsScriptDir)
+{
+ FileType projectPathFileType = getFileType(sibsScriptDir.c_str());
+ if(projectPathFileType == FileType::FILE_NOT_FOUND)
+ {
+ string errMsg = "Error: invalid SIBS_SCRIPT_DIR: ";
+ errMsg += toUtf8(sibsScriptDir);
+ perror(errMsg.c_str());
+ exit(2);
+ }
+ else if(projectPathFileType == FileType::REGULAR)
+ {
+ ferr <<"Error: Expected SIBS_SCRIPT_DIR path (" << sibsScriptDir << ") to be a directory, was a file" << endl;
+ exit(3);
+ }
+}
+
+static void validateSibsScriptPath(const FileString &sibsScriptFilepath)
+{
+ FileType projectPathFileType = getFileType(sibsScriptFilepath.c_str());
+ if(projectPathFileType == FileType::FILE_NOT_FOUND)
+ {
+ string errMsg = "Error: invalid sibs script: ";
+ errMsg += toUtf8(sibsScriptFilepath);
+ perror(errMsg.c_str());
+ exit(2);
+ }
+ else if(projectPathFileType == FileType::DIRECTORY)
+ {
+ ferr <<"Error: Expected sibs script at location (" << sibsScriptFilepath << ") to be a file, was a directory" << endl;
+ exit(3);
+ }
+}
+
static int packageProject(int argc, const _tinydir_char_t **argv)
{
- if(argc > 1)
- usagePackage();
+#if OS_TYPE != OS_TYPE_LINUX
+ fprintf(stderr, "Error: sibs package command is currently only available on linux\n");
+ exit(66);
+#endif
+ char *sibsScriptDirRaw = getenv("SIBS_SCRIPT_DIR");
+ if(!sibsScriptDirRaw)
+ {
+ fprintf(stderr, "Error: SIBS_SCRIPT_DIR needs to be defined. SIBS_SCRIPT_DIR should be the location to sibs scripts\n");
+ exit(67);
+ }
+ FileString sibsScriptDir = toFileString(string(sibsScriptDirRaw));
+ validateSibsScriptDir(sibsScriptDir);
+ FileString packageScriptPath = sibsScriptDir + TINYDIR_STRING("/package.py");
+ validateSibsScriptPath(packageScriptPath);
FileString projectPath;
- if(argc == 1)
- projectPath = argv[0];
+ PackagingType packagingType = PackagingType::NONE;
+
+ for(int i = 0; i < argc; ++i)
+ {
+ const _tinydir_char_t *arg = argv[i];
+ if(_tinydir_strcmp(arg, TINYDIR_STRING("--static")) == 0)
+ {
+ if(packagingType != PackagingType::NONE)
+ {
+ ferr << "Error: Project packaging type was defined more than once. First as " << asString(packagingType) << " then as " << "static" << endl;
+ usagePackage();
+ }
+ packagingType = PackagingType::STATIC;
+ }
+ else if(_tinydir_strcmp(arg, TINYDIR_STRING("--bundle")) == 0)
+ {
+ if(packagingType != PackagingType::NONE)
+ {
+ ferr << "Error: Project packaging type was defined more than once. First as " << asString(packagingType) << " then as " << "bundle" << endl;
+ usagePackage();
+ }
+ packagingType = PackagingType::BUNDLE;
+ }
+ else
+ {
+ if(!projectPath.empty())
+ {
+ ferr << "Error: Project path was defined more than once. First defined as " << projectPath << " then as " << arg << endl;
+ usagePackage();
+ }
+ projectPath = arg;
+ }
+ }
+
+ if(packagingType == PackagingType::NONE)
+ {
+ ferr << "Error: Project packaging type is not defined, expected to be either --static or --bundle" << endl;
+ usagePackage();
+ }
// TODO: If projectPath is not defined and working directory does not contain project.conf, then search every parent directory until one is found
if(projectPath.empty())
@@ -966,13 +1068,33 @@ static int packageProject(int argc, const _tinydir_char_t **argv)
SibsConfig sibsConfig(compiler, projectPath, OPT_LEV_RELEASE, false);
sibsConfig.showWarnings = true;
- sibsConfig.packaging = true;
+ sibsConfig.packaging = packagingType == PackagingType::STATIC;
int result = buildProject(projectPath, projectConfFilePath, sibsConfig);
if(result != 0)
return result;
- string packagePath = toUtf8(projectPath + TINYDIR_STRING("/sibs-build/package"));
- printf("Project %s was successfully packaged and can be found at %s\n", sibsConfig.getPackageName().c_str(), packagePath.c_str());
+ switch(packagingType)
+ {
+ case PackagingType::STATIC:
+ {
+ string packagePath = toUtf8(projectPath + TINYDIR_STRING("/sibs-build/package"));
+ printf("Project %s was successfully packaged and can be found at %s\n", sibsConfig.getPackageName().c_str(), packagePath.c_str());
+ break;
+ }
+ case PackagingType::BUNDLE:
+ {
+ string packagePath = toUtf8(projectPath + TINYDIR_STRING("/sibs-build/package"));
+ string executablePath = toUtf8(projectPath + TINYDIR_STRING("/sibs-build/release/") + sibsConfig.getPackageName());
+ FileString cmd = "python3 \"" + packageScriptPath + "\" \"" + executablePath + "\" \"" + packagePath + "\"";
+ Result<ExecResult> bundleResult = exec(cmd.c_str());
+ if(!bundleResult)
+ {
+ fprintf(stderr, "Error: failed to package project as a bundle, reason: %s\n", bundleResult.getErrMsg().c_str());
+ exit(77);
+ }
+ break;
+ }
+ }
return 0;
}