diff --git a/Toolchain/CMakeLists.txt b/Toolchain/CMakeLists.txt index 5c38308..c4bee8f 100644 --- a/Toolchain/CMakeLists.txt +++ b/Toolchain/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(Toolchain target_sources(Toolchain PRIVATE "Main.cpp" + "Source/Common.cpp" "Source/Command.cpp" "Source/Download.cpp" "Source/Package.cpp" diff --git a/Toolchain/Include/Command.hpp b/Toolchain/Include/Command.hpp index e69de29..6627144 100644 --- a/Toolchain/Include/Command.hpp +++ b/Toolchain/Include/Command.hpp @@ -0,0 +1,41 @@ +#ifndef TOOLCHAIN_COMMAND_HPP +#define TOOLCHAIN_COMMAND_HPP + +#include "Common.hpp" + +#include + +namespace Toolchain +{ + class Command + { + public: + Command(); + + virtual ~Command() = default; + + virtual bool Run() = 0; + }; + + class CMakeCommand: public Command + { + public: + + private: + fs::path mSource; + fs::path mBuild; + fs::path mInstall; + std::vector> mOptions; + }; + + class NinjaCommand: public Command + { + public: + + private: + fs::path mBuild; + + }; +} + +#endif // TOOLCHAIN_COMMAND_HPP diff --git a/Toolchain/Include/Common.hpp b/Toolchain/Include/Common.hpp index d30ec00..8abda7e 100644 --- a/Toolchain/Include/Common.hpp +++ b/Toolchain/Include/Common.hpp @@ -2,11 +2,22 @@ #define TOOLCHAIN_COMMON_HPP #include // IWYU pragma: export +#include namespace Toolchain { // NOLINTNEXTLINE: We're defining this to make life easier. namespace fs = std::filesystem; + + enum class FileType + { + Unknown, Zip, Tar, + }; + + // Note(3011): This function should handle both system paths, and URLs. + FileType DetectFileType(std::string_view file); + + std::string_view ToString(FileType fileType); } #endif // TOOLCHAIN_COMMON_HPP diff --git a/Toolchain/Include/Download.hpp b/Toolchain/Include/Download.hpp index e6b1fe3..71c8928 100644 --- a/Toolchain/Include/Download.hpp +++ b/Toolchain/Include/Download.hpp @@ -6,6 +6,8 @@ namespace Toolchain { bool DownloadFile(const fs::path &path, std::string_view url); + + // TODO(3011): Archive verification ... } #endif // TOOLCHAIN_DOWNLOAD_HPP diff --git a/Toolchain/Include/Package.hpp b/Toolchain/Include/Package.hpp index 374c3f9..0929ec0 100644 --- a/Toolchain/Include/Package.hpp +++ b/Toolchain/Include/Package.hpp @@ -1,6 +1,9 @@ #ifndef TOOLCHAIN_PACKAGE_HPP #define TOOLCHAIN_PACKAGE_HPP +#include +#include + namespace Toolchain { // The package should have a name, version, source url, and it should describe @@ -25,9 +28,16 @@ namespace Toolchain // with all the other projects. It will still require system dependencies for certain bits, but // that doesn't matter too much, as long as the host has a reasonably complete package registry. + using Step = std::vector; + class Package { + public: + Package(); + private: + std::string mName; + std::string mUrl; }; } diff --git a/Toolchain/Include/Stage.hpp b/Toolchain/Include/Stage.hpp new file mode 100644 index 0000000..9fc5d4f --- /dev/null +++ b/Toolchain/Include/Stage.hpp @@ -0,0 +1,23 @@ +#ifndef TOOLCHAIN_STAGE_HPP +#define TOOLCHAIN_STAGE_HPP + +#include "Common.hpp" +#include "Package.hpp" + +namespace Toolchain +{ + class Stage + { + public: + Stage(const fs::path &path); + + void AddPackage(const Package &package); + + void Build(); + + private: + fs::path mDirectory; + }; +} + +#endif // TOOLCHAIN_STAGE_HPP diff --git a/Toolchain/Main.cpp b/Toolchain/Main.cpp index 9e1e462..7e403e7 100644 --- a/Toolchain/Main.cpp +++ b/Toolchain/Main.cpp @@ -1,12 +1,83 @@ +#include "Common.hpp" #include "Download.hpp" +#include "Unpack.hpp" +#include +#include + +#include #include int main() { std::println("Hello World!"); - if (!Toolchain::DownloadFile("index.html", "https://3011.io/")) { + Toolchain::fs::create_directories("Archives"); + + if (!Toolchain::fs::exists("Archives/LLVM.tar.gz") && !Toolchain::DownloadFile("Archives/LLVM.tar.gz", "https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.8.tar.gz")) { std::println("Unlucky"); + return 1; + } + + Toolchain::fs::create_directories("Sources"); + + if (!Toolchain::fs::exists("Sources/LLVM") && !Toolchain::UnpackArchive("Archives/LLVM.tar.gz", "Sources/LLVM")) { + std::println("Unlucky"); + return 1; + } + + return 0; + + Toolchain::fs::create_directories("Builds"); + + Process::Process configure( + "cmake", { "-S", "Sources/LLVM/llvm", "-B", "Builds/LLVM", "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=Stage0", "-DLLVM_ENABLE_PROJECTS=all", "-DLLVM_ENABLE_RUNTIMES=all" }, + Process::ProcessOptions { + .StdinMode = Process::PipeMode::Null, + .StdoutMode = Process::PipeMode::Inherit, + .StderrMode = Process::PipeMode::Inherit, + .PathResolution = Process::PathMode::CurrentEnvironment, + .Environment = Process::CurrentEnvironment(), + .CloseOtherFds = true, + } + ); + configure.Wait(); + if (configure.ExitCode() != 0) { + std::println("Unlucky"); + return 1; + } + + Process::Process build ( + "ninja", { "-C", "Builds/LLVM", "-j4" }, + Process::ProcessOptions { + .StdinMode = Process::PipeMode::Null, + .StdoutMode = Process::PipeMode::Inherit, + .StderrMode = Process::PipeMode::Inherit, + .PathResolution = Process::PathMode::CurrentEnvironment, + .Environment = Process::CurrentEnvironment(), + .CloseOtherFds = true, + } + ); + build.Wait(); + if (build.ExitCode() != 0) { + std::println("Unlucky"); + return 1; + } + + Process::Process install ( + "ninja", { "-C", "Builds/LLVM", "install" }, + Process::ProcessOptions { + .StdinMode = Process::PipeMode::Null, + .StdoutMode = Process::PipeMode::Inherit, + .StderrMode = Process::PipeMode::Inherit, + .PathResolution = Process::PathMode::CurrentEnvironment, + .Environment = Process::CurrentEnvironment(), + .CloseOtherFds = true, + } + ); + install.Wait(); + if (install.ExitCode() != 0){ + std::println("Unlucky"); + return 1; } } diff --git a/Toolchain/Source/Common.cpp b/Toolchain/Source/Common.cpp new file mode 100644 index 0000000..46b38c3 --- /dev/null +++ b/Toolchain/Source/Common.cpp @@ -0,0 +1,43 @@ +#include "Common.hpp" + +#include + +namespace Toolchain +{ + FileType DetectFileType(std::string_view file) + { + if (std::size_t pos = file.find_last_of("?"); pos != std::string_view::npos) { + file = file.substr(0, pos); + } + + if (std::size_t pos = file.find_last_of("#"); pos != std::string_view::npos) { + file = file.substr(0, pos); + } + + if (std::size_t pos = file.find_last_of("/"); pos != std::string_view::npos) { + file = file.substr(pos + 1); + } + + if (file.ends_with(".zip")) { + return FileType::Zip; + } + + if (file.contains(".tar")) { + return FileType::Tar; + } + + return FileType::Unknown; + } + + std::string_view ToString(FileType fileType) + { + switch (fileType) { + case FileType::Unknown: + return "Unknown"; + case FileType::Zip: + return "zip"; + case FileType::Tar: + return "tar"; + } + } +} diff --git a/Toolchain/Source/Download.cpp b/Toolchain/Source/Download.cpp index 0107790..eb2709c 100644 --- a/Toolchain/Source/Download.cpp +++ b/Toolchain/Source/Download.cpp @@ -25,6 +25,7 @@ namespace Toolchain { bool DownloadFile(const fs::path &path, std::string_view url) { + std::println("Downloading {} to {}", url, path.c_str()); using offset_t = cpr::cpr_off_t; std::ofstream file(path, std::ios::binary); @@ -43,10 +44,38 @@ namespace Toolchain } ), cpr::ProgressCallback( - [path] (offset_t dt, offset_t dp, offset_t ut, offset_t up, std::intptr_t) { - double progress = double(dp) / double(dt); - std::print("\rDownloading {}: {:.2f}% ({}/{})", - path.c_str(), progress * 100, dp, dt); + [path] (offset_t dt, offset_t dp, offset_t, offset_t, std::intptr_t) { + if (!dt) { + std::string unit = "B"; + double current = dp; + if (current >= 1024.0) { + current /= 1024.0; + unit = "KiB"; + } + if (current >= 1024.0) { + current /= 1024.0; + unit = "MiB"; + } + std::print("\rDownloading {}: {}{}", path.c_str(), current, unit); + } + else { + std::string unit = "B"; + double current = dp; + double total = dt; + if (total >= 1024.0) { + total /= 1024.0; + current /= 1024.0; + unit = "KiB"; + } + if (total >= 1024.0) { + total /= 1024.0; + current /= 1024.0; + unit = "MiB"; + } + double progress = double(dp) / double(dt); + std::print("\rDownloading {}: {:.2f}% ({}/{}{})", + path.c_str(), progress * 100, current, total, unit); + } return true; } ) @@ -55,11 +84,25 @@ namespace Toolchain if (response.status_code != 200) { std::println("Download failed with status: {}, {}", - response.status_code, response.status_line); + response.status_code, response.status_line); return false; } file.close(); return true; } + + std::string DownloadContent(std::string_view url) + { + std::println("Downloading {}", url); + cpr::Response response = cpr::Get(cpr::Url(url)); + + if (response.status_code != 200) { + std::println("Download failed with status: {}, {}", + response.status_code, response.status_line); + return {}; + } + + return response.text; + } } diff --git a/Toolchain/Source/Unpack.cpp b/Toolchain/Source/Unpack.cpp index 3c10270..2f86a06 100644 --- a/Toolchain/Source/Unpack.cpp +++ b/Toolchain/Source/Unpack.cpp @@ -1,33 +1,107 @@ #include "Unpack.hpp" +#include "Common.hpp" #include +#include +#include + +namespace +{ + namespace fs = std::filesystem; + + bool UnpackZip(const fs::path &sourceArchive, const fs::path &destinationDirectory) + { + Process::Process unzip( + "unzip", { "-o", sourceArchive.string(), "-d", destinationDirectory.string() }, + Process::ProcessOptions { + .StdinMode = Process::PipeMode::Null, + .StdoutMode = Process::PipeMode::Null, + .StderrMode = Process::PipeMode::Null, + .PathResolution = Process::PathMode::CurrentEnvironment, + .Environment = {}, + .CloseOtherFds = true, + } + ); + unzip.Wait(); + return unzip.ExitCode() == 0; + } + + bool UnpackTar(const fs::path &sourceArchive, const fs::path &destinationDirectory) + { + Process::Process untar( + "tar", { "-xf", sourceArchive.string(), "-C", destinationDirectory.string() }, + Process::ProcessOptions { + .StdinMode = Process::PipeMode::Null, + .StdoutMode = Process::PipeMode::Null, + .StderrMode = Process::PipeMode::Null, + .PathResolution = Process::PathMode::CurrentEnvironment, + .Environment = {}, + .CloseOtherFds = true, + } + ); + untar.Wait(); + return untar.ExitCode() == 0; + } + + std::vector GetEntries(const fs::path &directory) + { + std::vector result; + for (const auto &entry: fs::directory_iterator(directory)) { + result.push_back(entry); + } + return result; + } +} + namespace Toolchain { bool UnpackArchive(const fs::path &sourceArchive, const fs::path &destinationDirectory) { - // Select the command based on the file extension + if (fs::exists(destinationDirectory)) { + return false; + } - // Extract the archive into a temporary directory + FileType fileType = DetectFileType(sourceArchive.string()); + if (fileType == FileType::Unknown || + (fileType != FileType::Zip && fileType != FileType::Tar) + ) { + return false; + } - // Check how many items were extracted (non-recursively, we only care about the top level) + fs::path stagingDirectory = destinationDirectory.parent_path() / "UnpackStaging"; + if (fs::exists(stagingDirectory)) { + return false; + } + fs::create_directories(stagingDirectory); - // If there was only one item, and it's a directory, it should be moved (and renamed) - // to the destination. In case it's a file, the destination should be created, and the - // file moved there. + if (fileType == FileType::Zip) { + if (!UnpackZip(sourceArchive, stagingDirectory)) { + fs::remove_all(stagingDirectory); + return false; + } + } + else if (fileType == FileType::Tar) { + if (!UnpackTar(sourceArchive, stagingDirectory)) { + fs::remove_all(stagingDirectory); + return false; + } + } - // If there are multiple items, the destination will be created, and all items moved into - // the destination. + // Note(3011): Rename will fail between different filesystems. + // This situation will likely never happen for intended workloads, + // but in case it does happen, a fallback to copy + remove will be necessary. - // Error handling and cleanup should be done so the temporary directory is always - // kept clean in case the program tries to use it for other stuff. + std::vector entries = GetEntries(stagingDirectory); + if (entries.size() == 1 && fs::is_directory(entries[0])) { + fs::rename(entries[0], destinationDirectory); + fs::remove_all(stagingDirectory); + return true; + } - // The reasoning behind this sort of behaviour is that the whole thing should always be as - // consistent as possible, we want everything to be as consistent as possible, and easy to - // work with afterwards. The point is that for the most part, the structure is know, so - // it is easy to proceed, and otherwise it should be easy to analyze the contents - // programatically. - - return false; + // Note(3011): The archive was either empty, or there are multiple entries, + // we don't really care at this point. + fs::rename(stagingDirectory, destinationDirectory); + return true; } }