#include #include #include #include "../include/FileUtil.hpp" #include "../include/Conf.hpp" #include "../include/Exec.hpp" #include "../backend/ninja/Ninja.hpp" using namespace std; 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 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 // TODO: Places that use PATH_MAX should be modified. A path CAN be longer than PATH_MAX... (does this include replacing tinydir.h?) // TODO: Remove install.sh when sibs supports installation of packages (so it can install itself) void usage() { printf("Usage: sibs COMMAND\n\n"); printf("Simple Build System for Native Languages\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 usageBuild() { printf("Usage: sibs build [project_path] [--debug|--release]\n\n"); printf("Build a sibs project\n\n"); printf("Options:\n"); printf(" project_path\t\t The directory containing a project.conf file - Optional (default: current working directory)\n"); printf(" --debug|--release\t\tOptimization level to build project and dependencies with (if not a system package) - Optional (default: --debug)\n"); printf("Examples:\n"); printf(" sibs build\n"); printf(" sibs build dirA/dirB\n"); printf(" sibs build --release\n"); printf(" sibs build dirA --release\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); } void validateDirectoryPath(const char *projectPath) { FileType projectPathFileType = getFileType(projectPath); if(projectPathFileType == FileType::FILE_NOT_FOUND) { string errMsg = "Invalid project path: "; errMsg += projectPath; perror(errMsg.c_str()); exit(2); } else if(projectPathFileType == FileType::REGULAR) { cerr <<"Expected project path (" << projectPath << ") to be a directory, was a file" << endl; exit(3); } } void validateFilePath(const char *projectConfPath) { FileType projectConfFileType = getFileType(projectConfPath); if(projectConfFileType == FileType::FILE_NOT_FOUND) { string errMsg = "Invalid project.conf path: "; errMsg += projectConfPath; perror(errMsg.c_str()); exit(4); } else if(projectConfFileType == FileType::DIRECTORY) { cerr << "Expected project path (" << projectConfPath << ") to be a file, was a directory" << endl; exit(5); } } const char *sourceFileExtensions[] = { "c", "cc", "cpp", "cxx" }; bool isSourceFile(tinydir_file *file) { if(!file->is_reg) return false; for(const char *sourceFileExtension : sourceFileExtensions) { if(_tinydir_strcmp(sourceFileExtension, file->extension) == 0) return true; } return false; } bool isPathSubPathOf(const string &path, const string &subPathOf) { return _tinydir_strncmp(path.c_str(), subPathOf.c_str(), subPathOf.size()) == 0; } int buildProject(int argc, const char **argv) { if(argc > 2) usageBuild(); OptimizationLevel optimizationLevel = OPT_LEV_NONE; string projectPath; for(int i = 0; i < argc; ++i) { const char *arg = argv[i]; if(strcmp(arg, "--debug") == 0) { if(optimizationLevel != OPT_LEV_NONE) { fprintf(stderr, "Error: Optimization level defined more than once. First defined as %s then as %s\n", asString(optimizationLevel), "debug"); usageBuild(); } optimizationLevel = OPT_LEV_DEBUG; } else if(strcmp(arg, "--release") == 0) { if(optimizationLevel != OPT_LEV_NONE) { fprintf(stderr, "Error: Optimization level defined more than once. First defined as %s then as %s\n", asString(optimizationLevel), "debug"); usageBuild(); } optimizationLevel = OPT_LEV_RELEASE; } else { if(!projectPath.empty()) { fprintf(stderr, "Error: Project path was defined more than once. First defined as %s then as %s\n", projectPath.c_str(), arg); usageBuild(); } projectPath = arg; } } if(optimizationLevel == OPT_LEV_NONE) optimizationLevel = OPT_LEV_DEBUG; // 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()) projectPath = "."; validateDirectoryPath(projectPath.c_str()); if(projectPath.back() != '/') projectPath += "/"; Result projectRealPathResult = getRealPath(projectPath.c_str()); if(!projectRealPathResult) { cerr << "Failed to get real path for: '" << projectPath << "': " << projectRealPathResult.getErrMsg() << endl; exit(40); } projectPath = projectRealPathResult.unwrap(); string projectConfFilePath = projectPath; projectConfFilePath += "/project.conf"; validateFilePath(projectConfFilePath.c_str()); SibsConfig sibsConfig(projectPath, optimizationLevel); Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if(result.isErr()) { cerr << "Failed to read config: " << result.getErrMsg() << endl; exit(6); } if(sibsConfig.getPackageName().empty()) { cerr << "project.conf is missing required field package.name" << endl; exit(10); } //string projectSrcPath = projectPath + "/src"; //validateDirectoryPath(projectSrcPath.c_str()); 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); FileWalkCallbackFunc collectSourceFiles = [&ninja, &sibsConfig, &collectSourceFiles](tinydir_file *file) { if(file->is_reg) { if (isSourceFile(file)) { ninja.addSourceFile(file->path + sibsConfig.getProjectPath().size()); } else { //printf("Ignoring non-source file: %s\n", file->path + projectPath.size()); } } else { // TODO: If compiling without "test" option, do not add test source dir to ninja. Ninja logic will then not build tests if(!sibsConfig.getTestPath().empty() && isPathSubPathOf(file->path, sibsConfig.getTestPath())) ninja.addTestSourceDir(file->path); else walkDir(file->path, collectSourceFiles); } }; walkDir(projectPath.c_str(), collectSourceFiles); string buildPath = projectPath + "/sibs-build/"; switch(sibsConfig.getOptimizationLevel()) { case OPT_LEV_DEBUG: buildPath += "debug"; break; case OPT_LEV_RELEASE: buildPath += "release"; break; } Result buildFileResult = ninja.build(sibsConfig, buildPath.c_str()); if(buildFileResult.isErr()) { cerr << "Failed to build ninja file: " << buildFileResult.getErrMsg() << endl; exit(7); } return 0; } void newProjectCreateMainDir(const string &projectPath) { Result createProjectDirResult = createDirectoryRecursive(projectPath.c_str()); if(createProjectDirResult.isErr()) { cerr << "Failed to create project main directory: " << createProjectDirResult.getErrMsg() << endl; exit(20); } } void createProjectSubDir(const string &dir) { Result createProjectDirResult = createDirectoryRecursive(dir.c_str()); if(createProjectDirResult.isErr()) { cerr << "Failed to create directory in project: " << createProjectDirResult.getErrMsg() << endl; exit(20); } } void createProjectFile(const string &projectFilePath, const string &fileContent) { Result fileOverwriteResult = fileOverwrite(projectFilePath.c_str(), fileContent.c_str()); if(fileOverwriteResult.isErr()) { 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 { 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{\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; }