From 92f24893a5e2d12d0c32662483117c7032623bf1 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 13 Dec 2017 18:35:52 +0100 Subject: Fix linking issue (linking in wrong order) Change program argument handling. Add new program argument "new" to create a new sibs project which creates a project with all necessary files and also uses git init (ignores failure, for example if git is not installed). Change build path from "build" to "sibs-build" to prevent name clashing since "build" is a common name for directories. --- backend/ninja/Ninja.cpp | 14 +-- include/Package.hpp | 12 +++ src/Conf.cpp | 8 +- src/FileUtil.cpp | 1 + src/GlobalLib.cpp | 17 +-- src/main.cpp | 268 ++++++++++++++++++++++++++++++++++-------------- 6 files changed, 228 insertions(+), 92 deletions(-) diff --git a/backend/ninja/Ninja.cpp b/backend/ninja/Ninja.cpp index 7fb07bf..87dd873 100644 --- a/backend/ninja/Ninja.cpp +++ b/backend/ninja/Ninja.cpp @@ -129,9 +129,9 @@ namespace backend } } - globalLibLinkerFlags += " "; + globalLibLinkerFlags += " '"; globalLibLinkerFlags += globalLibLinkerFlagsResult.unwrap(); - // TODO: If package doesn't exist, download it from github/server + globalLibLinkerFlags += "'"; } } @@ -139,7 +139,8 @@ namespace backend if(pkgConfigLinkerFlagsResult.isErr()) return pkgConfigLinkerFlagsResult; - linkerFlagCallbackFunc(pkgConfigLinkerFlagsResult.unwrap()); + if(!pkgConfigLinkerFlagsResult.unwrap().empty()) + linkerFlagCallbackFunc(pkgConfigLinkerFlagsResult.unwrap()); string allLinkerFlags = pkgConfigLinkerFlagsResult.unwrap(); allLinkerFlags += globalLibLinkerFlags; @@ -193,7 +194,7 @@ namespace backend } default: assert(false); - return Result::Err("NOT IMPLEMENTED YET!"); + return Result::Err("Building a dynamic library is not supported yet"); } vector objectNames; @@ -224,8 +225,9 @@ namespace backend string allLinkerFlags; Result linkerFlags = getLinkerFlags(dependencies, [&allLinkerFlags](const string &linkerFlag) { - allLinkerFlags += " "; + allLinkerFlags += " '"; allLinkerFlags += linkerFlag; + allLinkerFlags += "'"; }); if(linkerFlags.isErr()) return Result::Err(linkerFlags.getErrMsg()); @@ -256,7 +258,7 @@ namespace backend } default: assert(false); - return Result::Err("NOT IMPLEMENTED YET!"); + return Result::Err("Building a dynamic library is not supported yet"); } Result fileOverwriteResult = sibs::fileOverwrite(ninjaBuildFilePath.c_str(), sibs::StringView(result.data(), result.size())); diff --git a/include/Package.hpp b/include/Package.hpp index 130e987..106a48d 100644 --- a/include/Package.hpp +++ b/include/Package.hpp @@ -5,7 +5,19 @@ namespace sibs { enum class PackageType : int { + // Compile as executable when compiling project with this directly. + // If used in dependency, then fail because you can't (currently) have dependency to executable. EXECUTABLE, + + // Compile as static library when compiling project with this directly. + // If used in dependency, then this is the preferred library type, but the dependant project can override this. + STATIC, + + // Compile as dynamic library when compiling project with this directly. + // If used in dependency, then this is the preferred library type, but the dependant project can override this. + DYNAMIC, + + // Identical to DYNAMIC LIBRARY }; } diff --git a/src/Conf.cpp b/src/Conf.cpp index 8dd7608..1f439cd 100644 --- a/src/Conf.cpp +++ b/src/Conf.cpp @@ -378,11 +378,15 @@ namespace sibs const StringView &packageTypeStr = value.asSingle(); if(packageTypeStr.equals("executable")) packageType = PackageType::EXECUTABLE; + else if(packageTypeStr.equals("static")) + packageType = PackageType::STATIC; + else if(packageTypeStr.equals("dynamic")) + packageType = PackageType::DYNAMIC; else if(packageTypeStr.equals("library")) packageType = PackageType::LIBRARY; else { - string errMsg = "Expected package.type to be either 'executable' or 'library', was: "; + string errMsg = "Expected package.type to be either 'executable', 'static', 'dynamic' or 'library', was: "; errMsg += string(packageTypeStr.data, packageTypeStr.size); throw ParserException(errMsg); } @@ -409,7 +413,7 @@ namespace sibs void SibsConfig::finished() { if((int)packageType == -1) - throw ParserException("Missing required config package.type. Expected to be one either 'executable' or 'library'"); + throw ParserException("Missing required config package.type. Expected to be one either 'executable', 'static', 'dynamic' or 'library'"); finishedProcessing = true; } } \ No newline at end of file diff --git a/src/FileUtil.cpp b/src/FileUtil.cpp index 9a15938..899a1af 100644 --- a/src/FileUtil.cpp +++ b/src/FileUtil.cpp @@ -125,6 +125,7 @@ namespace sibs errMsg += strerror(error); return Result::Err(errMsg); } + setbuf(file, NULL); fwrite(data.data, 1, data.size, file); fclose(file); return Result::Ok(true); diff --git a/src/GlobalLib.cpp b/src/GlobalLib.cpp index 177f1b9..203cd0f 100644 --- a/src/GlobalLib.cpp +++ b/src/GlobalLib.cpp @@ -136,11 +136,19 @@ namespace sibs if(ninja.getSourceFiles().empty()) { - return Result::Ok(""); + return Result::Ok("No source files in dependency (header only library?)"); } else { - string debugBuildPath = packageDir + "/build/debug"; + string debugBuildPath = packageDir + "/sibs-build/debug"; + string staticLibPath = debugBuildPath; + staticLibPath += "/lib"; + staticLibPath += name; + staticLibPath += ".a"; + linkerFlagCallbackFunc(staticLibPath); + + // TODO: Use different directories depending on the project type, but .o build files should be in the same directory + // no matter what project type, since they are used for executables, static/dynamic libraries Result createBuildDirResult = createDirectoryRecursive(debugBuildPath.c_str()); if(createBuildDirResult.isErr()) return Result::Err(createBuildDirResult); @@ -153,11 +161,6 @@ namespace sibs if (buildResult.isErr()) return Result::Err(buildResult.getErrMsg()); - string staticLibPath = debugBuildPath; - staticLibPath += "/lib"; - staticLibPath += name; - staticLibPath += ".a"; - linkerFlagCallbackFunc(staticLibPath); return Result::Ok(staticLibPath); } } diff --git a/src/main.cpp b/src/main.cpp index 5c25414..9d6c88e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,9 @@ #include +#include +#include #include "../include/FileUtil.hpp" #include "../include/Conf.hpp" +#include "../include/Exec.hpp" #include "../backend/ninja/Ninja.hpp" using namespace std; @@ -9,17 +12,33 @@ using namespace sibs; // TODO: Fail if multiple versions of the same dependency is used // as linking will fail because of multiple definitions of the same thing -// TODO: Detect recursive dependencies and give error. +// TODO: Detect circular dependencies + +// TODO: Prevent infinite recursion in source file searching when there are symlinks. +// Either do not follow the symlinks or use a hash map with every searched directory +// to only go inside a directory once void usage() { - printf("usage: sibs COMMAND\n\n"); + printf("Usage: sibs COMMAND\n\n"); printf("Simple Build System for Native Languages\n\n"); - printf("Options:\n"); - printf(" -p\t\tPath to project (directory that contains main project.conf file)\n"); - printf(" -h\t\tPrint this help menu\n\n"); printf("Commands:\n"); printf(" build\t\tBuild a project that contains a project.conf file\n"); + printf(" new\t\tCreate a new project\n"); + exit(1); +} + +void usageNew() +{ + printf("Usage: sibs new <--exec|--static|--dynamic>\n\n"); + printf("Create a new sibs project\n\n"); + printf("Options:\n"); + printf(" project_name\t\tThe name of the project you want to create\n"); + printf(" --exec\t\t\tProject compiles to an executable\n"); + printf(" --static\t\t\tProject compiles to a static library\n"); + printf(" --dynamic\t\t\tProject compiles to a dynamic library\n"); + printf("Examples:\n"); + printf(" sibs new hello_world --exec\n"); exit(1); } @@ -28,12 +47,14 @@ void validateDirectoryPath(const char *projectPath) FileType projectPathFileType = getFileType(projectPath); if(projectPathFileType == FileType::FILE_NOT_FOUND) { - perror(projectPath); + string errMsg = "Invalid project path: "; + errMsg += projectPath; + perror(errMsg.c_str()); exit(2); } else if(projectPathFileType == FileType::REGULAR) { - printf("Expected project path (%s) to be a directory, was a file", projectPath); + cerr <<"Expected project path (" << projectPath << ") to be a directory, was a file" << endl; exit(3); } } @@ -43,17 +64,19 @@ void validateFilePath(const char *projectConfPath) FileType projectConfFileType = getFileType(projectConfPath); if(projectConfFileType == FileType::FILE_NOT_FOUND) { - perror(projectConfPath); + string errMsg = "Invalid project.conf path: "; + errMsg += projectConfPath; + perror(errMsg.c_str()); exit(4); } else if(projectConfFileType == FileType::DIRECTORY) { - printf("Expected project path (%s) to be a file, was a directory", projectConfPath); + cerr << "Expected project path (" << projectConfPath << ") to be a file, was a directory" << endl; exit(5); } } -const char *sourceFileExtensions[] = { "cc", "cpp", "cxx" }; +const char *sourceFileExtensions[] = { "c", "cc", "cpp", "cxx" }; bool isSourceFile(tinydir_file *file) { if(!file->is_reg) @@ -68,8 +91,20 @@ bool isSourceFile(tinydir_file *file) return false; } -void build(string projectPath) +int buildProject(int argc, const char **argv) { + if(argc > 1) + { + cerr << "Expected 'build' command to only be followed by one argument which is the path to a directory that contains a project.conf file, "; + cerr << "or none; in which case build would use the working directory as target directory" << endl << endl; + usage(); + } + + // TODO: If argc == 0 and working directory does not contain project.conf, then search every parent directory until one is found + string projectPath = "."; + if(argc == 1) + projectPath = argv[0]; + validateDirectoryPath(projectPath.c_str()); if(projectPath.back() != '/') projectPath += "/"; @@ -82,20 +117,36 @@ void build(string projectPath) Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if(result.isErr()) { - printf("Failed to read config: %s\n", result.getErrMsg().c_str()); + cerr << "Failed to read config: " << result.getErrMsg() << endl; exit(6); } if(sibsConfig.getPackageName().empty()) { - printf("project.conf is missing required field package.name\n"); + cerr << "project.conf is missing required field package.name" << endl; exit(10); } //string projectSrcPath = projectPath + "/src"; //validateDirectoryPath(projectSrcPath.c_str()); - backend::Ninja ninja; + PackageType packageType = sibsConfig.getPackageType(); + backend::Ninja::LibraryType libraryType; + switch(packageType) + { + case PackageType::EXECUTABLE: + libraryType = backend::Ninja::LibraryType::EXECUTABLE; + break; + case PackageType::STATIC: + libraryType = backend::Ninja::LibraryType::STATIC; + break; + case PackageType::DYNAMIC: + case PackageType::LIBRARY: + libraryType = backend::Ninja::LibraryType::DYNAMIC; + break; + } + + backend::Ninja ninja(libraryType); walkDirFilesRecursive(projectPath.c_str(), [&ninja, &projectPath](tinydir_file *file) { if (isSourceFile(file)) @@ -108,11 +159,11 @@ void build(string projectPath) } }); - string debugBuildPath = projectPath + "/build/debug"; + string debugBuildPath = projectPath + "/sibs-build/debug"; Result buildFileResult = ninja.createBuildFile(sibsConfig.getPackageName(), sibsConfig.getDependencies(), debugBuildPath.c_str()); if(buildFileResult.isErr()) { - printf("Failed to build ninja file: %s\n", buildFileResult.getErrMsg().c_str()); + cerr << "Failed to build ninja file: " << buildFileResult.getErrMsg() << endl; exit(7); } @@ -121,81 +172,144 @@ void build(string projectPath) { exit(8); } + + return 0; } -int main(int argc, const char **argv) +void newProjectCreateMainDir(const string &projectPath) { - string projectPath; - string command; - for(int i = 1; i < argc; ++i) + Result createProjectDirResult = createDirectoryRecursive(projectPath.c_str()); + if(createProjectDirResult.isErr()) { - const char *arg = argv[i]; - if(arg[0] == '-') - { - // Option - if(strcmp(arg, "-p") == 0) - { - if(!projectPath.empty()) - { - printf("-p option defined twice, should only be defined once\n"); - exit(1); - } - - if(i < argc - 1) - { - projectPath = argv[i + 1]; - ++i; - } - else - { - printf("Expected project path after option -p\n"); - exit(1); - } - } - else if(strcmp(arg, "-h") == 0) - { - usage(); - } - else - { - printf("Invalid option %s, type sibs -h to see valid options\n", arg); - exit(1); - } - } - else - { - if(!command.empty()) - { - printf("Found command twice. First as %s, then as %s\n", command.c_str(), arg); - exit(1); - } - command = arg; - } + cerr << "Failed to create project main directory: " << createProjectDirResult.getErrMsg() << endl; + exit(20); } +} - if(projectPath.empty()) +void createProjectSubDir(const string &dir) +{ + Result createProjectDirResult = createDirectoryRecursive(dir.c_str()); + if(createProjectDirResult.isErr()) { - Result projectPathResult = getCwd(); - if(projectPathResult.isErr()) - { - printf("%s\n", projectPathResult.getErrMsg().c_str()); - exit(11); - } - projectPath = projectPathResult.unwrap(); + cerr << "Failed to create directory in project: " << createProjectDirResult.getErrMsg() << endl; + exit(20); } +} - if(command == "build") - build(projectPath); - else if(command.empty()) +void createProjectFile(const string &projectFilePath, const string &fileContent) +{ + Result fileOverwriteResult = fileOverwrite(projectFilePath.c_str(), fileContent.c_str()); + if(fileOverwriteResult.isErr()) { - printf("No command provided, type sibs -h to see valid commands\n"); - exit(1); + cerr << "Failed to create project file: " << fileOverwriteResult.getErrMsg() << endl; + exit(20); } +} + +void newProjectCreateConf(const string &projectName, const string &projectType, const string &projectPath) +{ + string projectConfStr = "[package]\n"; + projectConfStr += "name = \"" + projectName + "\"\n"; + projectConfStr += "type = \"" + projectType + "\"\n"; + projectConfStr += "version = \"0.1.0\"\n\n"; + projectConfStr += "[dependencies]\n"; + + string projectConfPath = projectPath; + projectConfPath += "/project.conf"; + Result fileOverwriteResult = fileOverwrite(projectConfPath.c_str(), projectConfStr.c_str()); + if(fileOverwriteResult.isErr()) + { + cerr << "Failed to create project.conf: " << fileOverwriteResult.getErrMsg() << endl; + exit(20); + } +} + +// This can be replaced with createDirectory and fileOverwrite, but it's not important +// so there is no reason to do it (right now) +Result gitInitProject(const string &projectPath) +{ + string cmd = "git init '"; + cmd += projectPath; + cmd += "'"; + return exec(cmd.c_str()); +} + +int newProject(int argc, const char **argv) +{ + if(argc != 2) + { + cerr << "Expected 'new' command to be followed by two arguments - project name and type of project (--exec, --static or --dynamic)" << endl << endl; + usageNew(); + } + + Result cwdResult = getCwd(); + if(cwdResult.isErr()) + { + cerr << "Failed to get current working directory: " << cwdResult.getErrMsg() << endl; + exit(20); + } + + string projectName = argv[0]; + string projectPath = cwdResult.unwrap(); + projectPath += "/"; + projectPath += projectName; + bool projectPathExists = getFileType(projectPath.c_str()) != FileType::FILE_NOT_FOUND; + if(projectPathExists) + { + cerr << "Unable to create a new project at path '" << projectPath << "'. A file or directory already exists in the same location" << endl; + exit(20); + } + + const char *projectType = argv[1]; + + string projectTypeConf; + if(strcmp(projectType, "--exec") == 0) + projectTypeConf = "executable"; + else if(strcmp(projectType, "--static") == 0) + projectTypeConf = "static"; + else if(strcmp(projectType, "--dynamic") == 0) + projectTypeConf = "dynamic"; else { - printf("Invalid command %s, expected: build\n", command.c_str()); - exit(1); + cerr << "Expected project type to be either --exec, --static or --dynamic; was: " << projectType << endl << endl; + usageNew(); + } + + newProjectCreateMainDir(projectPath); + newProjectCreateConf(projectName, projectTypeConf, projectPath); + createProjectSubDir(projectPath + "/src"); + createProjectSubDir(projectPath + "/include"); + createProjectFile(projectPath + "/src/main.cpp", "#include \n\nint main()\n{ return 0;\n}\n"); + // We are ignoring git init result on purpose. If it fails, just ignore it; not important + gitInitProject(projectPath); + return 0; +} + +int main(int argc, const char **argv) +{ + unordered_map param; + unordered_set flags; + + for(int i = 1; i < argc; ++i) + { + const char *arg = argv[i]; + int subCommandArgCount = argc - i - 1; + const char **subCommandArgPtr = argv + i + 1; + if(strcmp(arg, "build") == 0) + { + return buildProject(subCommandArgCount, subCommandArgPtr); + } + else if(strcmp(arg, "new") == 0) + { + return newProject(subCommandArgCount, subCommandArgPtr); + } + else + { + cerr << "Expected command to be either 'build' or 'new', was: " << arg << endl << endl; + usage(); + } } + usage(); return 0; } -- cgit v1.2.3