Commit d6c5fb72 authored by Bat's avatar Bat

Extracted libpoulp: fix #49

parent c02c465f
......@@ -23,12 +23,9 @@ json-vala = "1.7.x"
"libsoup-2.4" = "*"
semver = "1.x.x"
toml-glib = "~0.1.0"
libpoulp = "0.x.x"
[scripts]
install = [
"install -m 777 build/poulp /usr/bin/"
]
# Multiple targets are not supported yet. :(
lib = [
"valac src/logger.vala src/manifest.vala src/repository.vala src/utils.vala --library=build/libpoulp -H build/libpoulp.h -X -fPIC -X -shared -o build/libpoulp.so --pkg semver-0.1.0 --pkg json-glib-1.0 --pkg gee-0.8 --pkg gio-2.0"
]
namespace Poulp {
/**
* Represents the result of an application build
*/
public class AppBuildOutput : BuildOutput {
/**
* The (base)name of the binary file
*/
public string bin_file { get; set; }
/**
* {@inheritDoc}
*/
public override void set_output (string name) {
this.name = name;
#if WIN32
this.bin_file = name + ".exe";
#else
this.bin_file = name;
#endif
}
/**
* {@inheritDoc}
*/
public override async bool install () {
try {
Manifest man = new Manifest.for_path (Path.build_filename (this.project_path, "poulp.toml"));
if (man.scripts.has_key ("pre-install")) {
return yield new ScriptRunner ().run (man.scripts["pre-install"]);
}
if (man.scripts.has_key ("install")) {
return yield new ScriptRunner ().run (man.scripts["install"]);
} else {
string install_dir = Environment.get_variable ("POULP_BIN_DIR");
File bin = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.bin_file));
File dir = File.new_for_path (Path.build_filename (install_dir, this.bin_file));
bin.copy (dir, FileCopyFlags.OVERWRITE | FileCopyFlags.ALL_METADATA);
}
if (man.scripts.has_key ("post-install")) {
return yield new ScriptRunner ().run (man.scripts["post-install"]);
}
return true;
} catch (Error err) {
Logger.log_error (err.message);
return false;
}
}
}
}
using Gee;
namespace Poulp {
/**
* Represents the result of a build.
*/
public abstract class BuildOutput : Object {
/**
* false if build failed, true otherwise
*/
public bool success { get; set; default = true; }
/**
* The absolute path to the projects root
*/
public string project_path { get; set; }
/**
* The directory where the output can be found (`build` by default)
*/
public string build_dir { get; set; }
/**
* The name of the output
*/
public string name { get; set; }
/**
* The bsolute path to the output
*/
public string output_path {
owned get {
return Path.build_filename (this.project_path, this.build_dir, this.name);
}
}
/**
* Sets the output files for a project
*
* @param name The name of the project
*/
public abstract void set_output (string name);
/**
* Installs the output file(s)
*/
public abstract async bool install ();
}
}
This diff is collapsed.
namespace Poulp {
/**
* A command that can be run by poulp
*/
public interface Command : Object {
/**
* Called when the command is ran by the user.
*
* @param args Extra arguments passed to the command
*/
public abstract async bool run (string[] args);
/**
* Gives an help message about this command.
*
* @return The help message.
*/
public abstract string help (bool is_short);
}
}
using Gee;
using SemVer;
namespace Poulp {
/**
* Installs packages
*/
public class Installer : Object {
/**
* A list of repositories where we search packages
*/
public ArrayList<Repository> repositories { get; set; default = new ArrayList<Repository> (); }
/**
* Emitted when a package isn't found in any remote repository
*/
public signal void not_found (string pkg);
/**
* Installs a package
*
* @param pkg The package to install
* @param range A {@link SemVer.Range} that should contain a version of the package
*/
public async bool install (string pkg, Range range) {
foreach (var repo in this.repositories) {
if (repo.packages.has_key (pkg)) {
try {
print ("Cloning %s...\n", pkg);
string clone_dir = Path.build_filename (Environment.get_tmp_dir (), "poulp", pkg);
File dir = File.new_for_path (clone_dir);
// removing files if they are already here
if (dir.query_exists ()) {
foreach (string file in recursive_ls (dir.get_path (), true, true)) {
File child = File.new_for_path (file);
child.delete ();
}
} else {
dir.make_directory_with_parents ();
}
int exit_code = yield spawn_async ("git clone %s %s -q".printf (repo.packages[pkg], clone_dir));
if (exit_code != 0) {
Logger.log_error ("Can't clone %s", pkg);
return false;
}
print ("Building %s\n", pkg);
string tags = "";
Process.spawn_sync (clone_dir, { "git", "tag" }, null, SpawnFlags.SEARCH_PATH, null, out tags);
SemVer.Version ver = new SemVer.Version (0, 0, 0);
foreach (string tag in tags.split ("\n")) {
if (valid (tag)) {
var v = new SemVer.Version.parse (tag);
if (v in range && v.gt (ver)) {
ver = v;
}
}
}
if (ver.eq (new SemVer.Version (0, 0, 0))) {
Logger.log_error ("No matching version of %s found.", pkg);
return false;
}
yield spawn_async (@"git checkout $ver -q", clone_dir);
Builder bld = new Builder (new Manifest.for_path (Path.build_filename (clone_dir, "poulp.toml")));
BuildOutput build = yield bld.build (clone_dir);
yield build.install ();
} catch (Error err) {
Logger.log_error (err.message);
}
}
}
return true;
}
}
}
using Gee;
namespace Poulp {
/**
* The result of a library build
*/
public class LibBuildOutput : BuildOutput {
/**
* The (base)name of the .vapi file
*/
public string vapi_file { get; set; }
/**
* The (base)name of the .deps file
*/
public string deps_file { get; set; }
/**
* The (base)name of the .h file
*/
public string header_file { get; set; }
/**
* The (base)name of the .so/.dll file
*/
public string lib_file { get; set; }
/**
* The (base)name of the .pc file
*/
public string pc_file { get; set; }
/**
* {@inheritDoc}
*/
public override void set_output (string name) {
this.name = name;
this.vapi_file = name + ".vapi";
this.deps_file = name + ".deps";
this.header_file = name + ".h";
#if WIN32
string ext = ".exe";
#else
string ext = ".so";
#endif
this.lib_file = "lib" + name + ext;
this.pc_file = name + ".pc";
}
/**
* {@inheritDoc}
*/
public override async bool install () {
try {
Manifest man = new Manifest.for_path (Path.build_filename (this.project_path, "poulp.toml"));
if (man.scripts.has_key ("pre-install")) {
return yield new ScriptRunner ().run (man.scripts["pre-install"]);
}
if (man.scripts.has_key ("install")) {
return yield new ScriptRunner ().run (man.scripts["install"]);
} else {
HashMap<string, string> dest = new HashMap<string, string> ();
dest["vapi"] = Path.build_filename (Environment.get_variable ("POULP_VAPI_DIR"), this.vapi_file);
dest["deps"] = Path.build_filename (Environment.get_variable ("POULP_VAPI_DIR"), this.deps_file);
dest["pc"] = Path.build_filename (Environment.get_variable ("POULP_PKG_DIR"), this.pc_file);
dest["lib"] = Path.build_filename (Environment.get_variable ("POULP_LIB_DIR"), this.lib_file);
dest["header"] = Path.build_filename (Environment.get_variable ("POULP_HEADERS_DIR"), this.name, this.header_file);
HashMap<string, File> files = new HashMap<string, File> ();
files["vapi"] = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.vapi_file));
files["deps"] = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.deps_file));
files["header"] = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.header_file));
files["lib"] = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.lib_file));
files["pc"] = File.new_for_path (Path.build_filename (this.project_path, this.build_dir, this.pc_file));
foreach (var entry in dest.entries) {
File dir = File.new_for_path (entry.value);
files[entry.key].copy (dir, FileCopyFlags.OVERWRITE | FileCopyFlags.ALL_METADATA);
}
}
if (man.scripts.has_key ("post-install")) {
return yield new ScriptRunner ().run (man.scripts["post-install"]);
}
return true;
} catch (Error err) {
Logger.log_error (err.message);
return false;
}
}
}
}
namespace Poulp {
/**
* Defines the allowed colors.
*/
namespace LogColor {
public const string NORMAL = "";
public const string RESET = "\033[m";
public const string BOLD = "\033[1m";
public const string RED = "\033[31m";
public const string GREEN = "\033[32m";
public const string YELLOW = "\033[33m";
public const string BLUE = "\033[34m";
public const string MAGENTA = "\033[35m";
public const string CYAN = "\033[36m";
public const string BOLD_RED = "\033[1;31m";
public const string BOLD_GREEN = "\033[1;32m";
public const string BOLD_YELLOW = "\033[1;33m";
public const string BOLD_BLUE = "\033[1;34m";
public const string BOLD_MAGENTA = "\033[1;35m";
public const string BOLD_CYAN = "\033[1;36m";
public const string BG_RED = "\033[41m";
public const string BG_GREEN = "\033[42m";
public const string BG_YELLOW = "\033[43m";
public const string BG_BLUE = "\033[44m";
public const string BG_MAGENTA = "\033[45m";
public const string BG_CYAN = "\033[46m";
}
/**
* An utility to log beautiful and coherent messages.
*/
public class Logger : Object {
/**
* This method prints a message with full colored line.
*
* @param color The color to apply to the message.
* @param message The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static void logc (string color, string message, ...) {
stdout.puts (color + message.vprintf (va_list ()) + LogColor.RESET + "\n");
}
/**
* This method do the same as logc, but avoid adding a newline (\n) at the end of the message.
*
* @param color The color to apply to the message.
* @param message The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static void logcc (string color, string message, ...) {
stdout.puts (color + message.vprintf (va_list ()) + LogColor.RESET);
}
/**
* This method prints a message.
*
* @param message The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static void log (string message, ...) {
stdout.puts (message.vprintf (va_list ()) + "\n");
}
/**
* Logs an error.
*
* @param error The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static void log_error (string error, ...) {
Logger.logc (LogColor.RED, "[ERROR] " + error.vprintf (va_list ()));
}
/**
* Logs a warning.
*
* @param warning The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static void log_warning (string warning, ...) {
Logger.logc (LogColor.YELLOW, "[WARNING] " + warning.vprintf (va_list ()));
}
/**
* Ask the user to enter an input.
*
* @param message The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
*/
public static string ask (string message, ...) {
Logger.logcc (LogColor.BOLD_CYAN, message, va_list ());
string input = stdin.read_line ();
if (input == "" || input == null) {
return "";
}
return input;
}
/**
* Ask the user to confirm or decline. [Y/n]
*
* @param message The message to print.
* @param args A va_list that'll be vsprintf'ed to message.
* @return true if user answered by Y, false if n.
*/
public static bool askyn (string message, ...) {
string input = "";
bool is_valid_input = false;
Logger.logcc (LogColor.BOLD_CYAN, message + " [Y/n] ", va_list ());
// while the user didn't choosed between yes and no
while (is_valid_input == false) {
input = stdin.read_line ().down ();
if (input == "y" || input == "n") {
is_valid_input = true;
break;
} else {
Logger.logcc (LogColor.BOLD_YELLOW, "Invalid choice, please choose beetwen Y(es) or n(o). ");
}
}
return (input == "y");
}
}
}
This diff is collapsed.
......@@ -53,65 +53,4 @@ namespace Poulp {
return 1;
}
}
void setup_env () {
Environment.set_variable ("VALA_VERSION", get_vala_version ().to_string (), false);
Environment.set_variable ("VALA_API_VERSION", string.joinv (".", get_vala_version ().to_string ().split (".")[0:2]), false);
#if WIN32
Environment.set_variable ("POULP_DATA_DIR", "C:\\ProgramData\\poulp", false);
Environment.set_variable ("POULP_BIN_DIR", "C:\\ProgramFiles\\", false);
Environment.set_variable ("POULP_HEADERS_DIR", "C:\\vala-0.20.1\\include", false);
Environment.set_variable ("POULP_LIB_DIR", "C:\\vala-0.20.1\\lib", false);
Environment.set_variable ("POULP_VAPI_DIR", "C:\\ProgramData\\vala-0.20.1\\vapi", false);
Environment.set_variable ("POULP_PKG_DIR", "C:\\vala-0.20.1\\pkgconfig", false);
#else
Environment.set_variable ("POULP_DATA_DIR", "/etc/poulp/", false);
Environment.set_variable ("POULP_BIN_DIR", "/usr/bin/", false);
Environment.set_variable ("POULP_HEADERS_DIR", "/usr/include/", false);
Environment.set_variable ("POULP_LIB_DIR", "/usr/lib64/", false);
Environment.set_variable ("POULP_VAPI_DIR", "/usr/share/vala/vapi", false);
Environment.set_variable ("POULP_PKG_DIR", "/usr/lib64/pkgconfig/", false);
#endif
}
/**
* Setup the default configuration and data files
*/
bool create_directories (bool force) {
const string default_repos = "https://gitlab.com/Bat41/poulp-repo-test/raw/master/dist\n";
const string default_projects = """[
{
"id": "console",
"name": "Console",
"description": "A very basic console application",
"files": [ "main.vala" ]
}
]""";
const string console = """
int main () {
print ("Hello, world!");
return 0;
}
""";
try {
File poulp_dir = File.new_for_path (get_poulp_dir ());
if (!poulp_dir.query_exists () || force) {
poulp_dir.make_directory ();
FileUtils.set_contents (Path.build_filename (get_poulp_dir (), "repos"), default_repos);
DirUtils.create_with_parents (Path.build_filename (get_poulp_dir (), "files"), 0777);
FileUtils.set_contents (Path.build_filename (get_poulp_dir (), "files"), "[]");
DirUtils.create_with_parents (Path.build_filename (get_poulp_dir (), "projects"), 0777);
FileUtils.set_contents (Path.build_filename (get_poulp_dir (), "projects", "projects.json"), default_projects);
DirUtils.create_with_parents (Path.build_filename (get_poulp_dir (), "projects", "console"), 0777);
FileUtils.set_contents (Path.build_filename (get_poulp_dir (), "projects", "console", "main.vala"), console);
}
return true;
} catch (Error err) {
Logger.log_error ("Failed to create necessary directories. Try to run the command as an administrator.");
return false;
}
}
}
using Gee;
namespace Poulp {
/**
* A class to manage a remote repository.
*/
public class Repository : Object {
/**
* A list of packages.
*
* Keys are packages names and values their Git repository URL.
*/
public HashMap<string, string> packages { get; set; default = new HashMap<string, string> (); }
/**
* The name of the remote repository
*/
public string name { get; set; }
/**
* Creates a new {@link Poulp.Repository} from a {@link Json.Object}
*
* @param root The packages.json of this repository
*/
public Repository.from_json (Json.Object root) {
this.name = root["name"].as<string> ();
foreach (var pkg in root["packages"].as_object ().properties) {
this.packages[pkg.name] = pkg.value.as<string> ();
}
}
/**
* Gives a list of all registred repositories
*/
public static async ArrayList<Repository> load_known () throws Error {
ArrayList<Repository> known = new ArrayList<Repository> ();
string repo_list;
try {
FileUtils.get_contents (Path.build_filename (get_poulp_dir (), "repos"), out repo_list);
foreach (string url in repo_list.split ("\n")) {
if (url.length != 0) {
var repo_content = yield download (Path.build_path ("/", url, "packages.json"));
Json.Parser parser = new Json.Parser ();
parser.load_from_data ((string)repo_content);
var repo = new Repository.from_json (parser.root.as_object ());
known.add (repo);
}
}
} catch (Error err) {
throw err;
}
return known;
}
}
}
using Gee;
namespace Poulp {
/**
* Runs a script
*/
public class ScriptRunner : Object {
/**
* The environment variables
*/
public HashMap<string, string> env { get; set; default = new HashMap<string, string> (); }
/**
* The working directory for scripts commands
*/
public string cwd { get; set; default = Environment.get_current_dir () ; }
/**
* Loads environment from a raw environment (e.g. `k=v` list)
*/
public void load_env (string[] raw_env = Environ.get ()) {
foreach (string variable in raw_env) {
if (!("=" in variable)) {
continue;
}
string[] part = variable.split ("=");
this.env[part[0]] = part[1];
}
}
/**
* Compiles environment into a `k=v` list
*/
public string[] compile_env () {
string[] res = {};
foreach (var entry in this.env.entries) {
res += "%s=%s".printf (entry.key, Shell.quote (entry.value));
}
return res;
}
/**
* Runs a list of commands
*
* @param commands The commands to run
*/
public async bool run (ArrayList<string> commands) {
bool res = true;
try {
Manifest man = Manifest.get_for_current_project ();
// wet some useful environment variables before running anything
this.env["PROJECT_NAME"] = man.name;
this.env["PROJECT_VERSION"] = man.version.to_string ();
this.env["PROJECT_DESCRIPTION"] = man.description == null ? "" : man.description;
this.env["PROJECT_LICENSE"] = man.license == null ? "" : man.license;
this.env["PROJECT_HOMEPAGE"] = man.homepage == null ? "" : man.homepage;
this.env["PROJECT_OUTPUT"] = man.output == null ? man.name + "-" + man.version.to_string () : man.output;
foreach (string cmd in commands) {
// use PowerShell/bach to make it possible to use wildcards and Co.
#if WIN32
string[] shell = { "start", "powershell", "-NoExit", "-Command", Shell.quote (cmd) };
#else
string[] shell = { "/bin/bash", "-c", Shell.quote (cmd) };
#endif
Pid pid;
Process.spawn_async_with_pipes (this.cwd,
shell,
this.compile_env (),
SpawnFlags.SEARCH_PATH | SpawnFlags.CHILD_INHERITS_STDIN | SpawnFlags.DO_NOT_REAP_CHILD,
null,
out pid);
int exit_code = 42;
ChildWatch.add (pid, (_pid, status) => {
Process.close_pid (_pid);
exit_code = status;
this.run.callback ();
});
yield;
res = res && (exit_code == 0);
}
} catch (Error err) {
Logger.log_error (err.message);
res = false;
}
return res;
}
}
}
namespace Poulp {
/**
* The errors that can be encountered by poulp.
*/
public errordomain PoulpError {
INVALID_MANIFEST,
NO_MANIFEST,
REPO_ERROR,
PACKAGE_NOT_FOUND
}
public errordomain BuildError {
INVALID_VALA_VERSION
}
/**
* Recursively list the content of a directory.
*
* @param dir The directory to explore
* @return A {@link GLib.string} array, containing the path of the files found, relatives to the base directory
*/
public string[] recursive_ls (string dir, bool list_dirs = false, bool git = false) throws GLib.Error {
string[] res = {};
try {
var src_file = File.new_for_path (dir);
GLib.FileType src_type = src_file.query_file_type (GLib.FileQueryInfoFlags.NONE);
if (src_type == GLib.FileType.DIRECTORY && (!src_file.get_path ().contains ("/.git") || git)) { // we explore all the directories, excepted .git
var enumerator = src_file.enumerate_children (GLib.FileAttribute.STANDARD_NAME, GLib.FileQueryInfoFlags.NONE);
for (GLib.FileInfo? info = enumerator.next_file (); info != null ; info = enumerator.next_file ()) {
foreach (string child in recursive_ls (GLib.Path.build_filename (dir, info.get_name ()), list_dirs, git)) {
res += child;
}
}
if (list_dirs) {
res += dir;
}
} else if (src_type == GLib.FileType.REGULAR ) { // we add files to the result
res += src_file.get_path ();
}
return res;
} catch (Error err) {
Logger.log_error (err.message);
return {};
}
}
/**
* Gives the poulp directory.
*