| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
-
- #include "analyzer.h"
- #include "generator.h"
- #include "Machine.h"
- using namespace dsr;
- static Extension extensionFromString(ReadableString extensionName) {
- String upperName = string_upperCase(string_removeOuterWhiteSpace(extensionName));
- Extension result = Extension::Unknown;
- if (string_match(upperName, U"H")) {
- result = Extension::H;
- } else if (string_match(upperName, U"HPP")) {
- result = Extension::Hpp;
- } else if (string_match(upperName, U"C")) {
- result = Extension::C;
- } else if (string_match(upperName, U"CPP")) {
- result = Extension::Cpp;
- }
- return result;
- }
- struct HashGenerator {
- uint64_t a = 0x8C2A03D4;
- uint64_t b = 0xF42B1583;
- uint64_t c = 0xA6815E74;
- uint64_t d = 0x634B20F6;
- uint64_t e = 0x12C49B72;
- uint64_t f = 0x06E1F489;
- uint64_t g = 0xA8D24954;
- uint64_t h = 0x19CF53AA;
- HashGenerator() {}
- void feedByte(uint64_t input) {
- // Write input
- a = a ^ (input << ((e >> 12u) % 56u));
- b = b ^ (input << ((f >> 18u) % 56u));
- c = c ^ (input << ((g >> 15u) % 56u));
- d = d ^ (input << ((h >> 5u) % 56u));
- // Select bits
- uint64_t e = (a & c) | (b & ~c);
- uint64_t f = (c & b) | (d & ~b);
- // Multiply
- uint64_t g = (e >> 32) * (f & 0xFFFFFFFF);
- uint64_t h = (f >> 32) * (e & 0xFFFFFFFF);
- // Add
- a = a ^ (b >> ((input) % 3u)) + (c >> ((h >> 25u) % 4u));
- b = b ^ (c >> ((g >> 36u) % 6u)) + (d >> ((input ^ 0b10101101) % 5u));
- c = c ^ g;
- d = d ^ h;
- }
- uint64_t getHash64() {
- return a ^ (b << 7) ^ (c << 19) ^ (d << 24);
- }
- };
- static uint64_t checksum(ReadableString text) {
- HashGenerator generator;
- for (int64_t i = 0; i < string_length(text); i++) {
- DsrChar c = text[i];
- generator.feedByte((c >> 24) & 0xFF);
- generator.feedByte((c >> 16) & 0xFF);
- generator.feedByte((c >> 8) & 0xFF);
- generator.feedByte(c & 0xFF);
- }
- return generator.getHash64();
- }
- static uint64_t checksum(const Buffer& buffer) {
- HashGenerator generator;
- SafePointer<uint8_t> data = buffer_getSafeData<uint8_t>(buffer, "checksum input buffer");
- for (int64_t i = 0; i < buffer_getSize(buffer); i++) {
- generator.feedByte(data[i]);
- }
- return generator.getHash64();
- }
- static int64_t findDependency(ProjectContext &context, ReadableString findPath) {
- for (int64_t d = 0; d < context.dependencies.length(); d++) {
- if (string_match(context.dependencies[d].path, findPath)) {
- return d;
- }
- }
- return -1;
- }
- static void resolveConnection(ProjectContext &context, Connection &connection) {
- connection.dependencyIndex = findDependency(context, connection.path);
- }
- static void resolveDependency(ProjectContext &context, Dependency &dependency) {
- for (int64_t l = 0; l < dependency.links.length(); l++) {
- resolveConnection(context, dependency.links[l]);
- }
- for (int64_t i = 0; i < dependency.includes.length(); i++) {
- resolveConnection(context, dependency.includes[i]);
- }
- }
- void resolveDependencies(ProjectContext &context) {
- for (int64_t d = 0; d < context.dependencies.length(); d++) {
- resolveDependency(context, context.dependencies[d]);
- }
- }
- static String findSourceFile(ReadableString headerPath, bool acceptC, bool acceptCpp) {
- if (file_hasExtension(headerPath)) {
- ReadableString extensionlessPath = file_getExtensionless(headerPath);
- String cPath = extensionlessPath + U".c";
- String cppPath = extensionlessPath + U".cpp";
- if (acceptC && file_getEntryType(cPath) == EntryType::File) {
- return cPath;
- } else if (acceptCpp && file_getEntryType(cppPath) == EntryType::File) {
- return cppPath;
- }
- }
- return U"";
- }
- static void flushToken(List<String> &target, String ¤tToken) {
- if (string_length(currentToken) > 0) {
- target.push(currentToken);
- currentToken = U"";
- }
- }
- static void tokenize(List<String> &target, ReadableString line) {
- String currentToken;
- for (int64_t i = 0; i < string_length(line); i++) {
- DsrChar c = line[i];
- DsrChar nextC = line[i + 1];
- if (c == U'#' && nextC == U'#') {
- // Appending tokens using ##
- i++;
- } else if (c == U'#' || c == U'(' || c == U')' || c == U'[' || c == U']' || c == U'{' || c == U'}') {
- // Atomic token of a single character
- flushToken(target, currentToken);
- string_appendChar(currentToken, c);
- flushToken(target, currentToken);
- } else if (c == U' ' || c == U'\t') {
- // Whitespace
- flushToken(target, currentToken);
- } else {
- string_appendChar(currentToken, c);
- }
- }
- flushToken(target, currentToken);
- }
- // When CACHED_ANALYSIS is enabled, files will only be analyzed once per session, by remembering them from previous projects.
- // If features that require a different type of analysis per project are implemented, this can easily be turned off.
- #define CACHED_ANALYSIS
- #ifdef CACHED_ANALYSIS
- // Remembering previous results from analyzing the same files.
- List<Dependency> analysisCache;
- #endif
- void analyzeFile(Dependency &result, ReadableString absolutePath, Extension extension) {
- #ifdef CACHED_ANALYSIS
- // Check if the file has already been analyzed.
- for (int c = 0; c < analysisCache.length(); c++) {
- if (string_match(analysisCache[c].path, absolutePath)) {
- // Clone all the results to keep projects separate in memory for safety.
- result = analysisCache[c];
- return;
- }
- }
- #endif
- // Get the file's binary content.
- Buffer fileBuffer = file_loadBuffer(absolutePath);
- // Get the checksum
- result.contentChecksum = checksum(fileBuffer);
- if (extension == Extension::H || extension == Extension::Hpp) {
- // The current file is a header, so look for an implementation with the corresponding name.
- String sourcePath = findSourceFile(absolutePath, extension == Extension::H, true);
- // If found:
- if (string_length(sourcePath) > 0) {
- // Remember that anything using the header will have to link with the implementation.
- result.links.pushConstruct(sourcePath);
- }
- }
- // Interpret the file's content.
- String sourceCode = string_loadFromMemory(fileBuffer);
- String parentFolder = file_getRelativeParentFolder(absolutePath);
- List<String> tokens;
- bool continuingLine = false;
- int64_t lineNumber = 0;
- string_split_callback(sourceCode, U'\n', true, [&result, &parentFolder, &tokens, &continuingLine, &absolutePath, &lineNumber](ReadableString line) {
- lineNumber++;
- if (line[0] == U'#' || continuingLine) {
- tokenize(tokens, line);
- // Continuing pre-processing line using \ at the end.
- continuingLine = line[string_length(line) - 1] == U'\\';
- } else {
- continuingLine = false;
- }
- if (!continuingLine && tokens.length() > 0) {
- if (tokens.length() >= 3) {
- if (string_match(tokens[1], U"include")) {
- if (tokens[2][0] == U'\"') {
- String relativePath = string_unmangleQuote(tokens[2]);
- String absoluteHeaderPath = file_getTheoreticalAbsolutePath(relativePath, parentFolder, LOCAL_PATH_SYNTAX);
- if (file_getEntryType(absoluteHeaderPath) != EntryType::File) {
- throwError(U"Failed to find ", absoluteHeaderPath, U" from line ", lineNumber, U" in ", absolutePath, U"\n");
- } else {
- result.includes.pushConstruct(absoluteHeaderPath, lineNumber);
- }
- }
- }
- }
- tokens.clear();
- }
- });
- }
- void analyzeFromFile(ProjectContext &context, ReadableString absolutePath) {
- if (findDependency(context, absolutePath) != -1) {
- // Already analyzed the current entry. Abort to prevent duplicate dependencies.
- return;
- }
- Extension extension = extensionFromString(file_getExtension(absolutePath));
- if (extension != Extension::Unknown) {
- // Create a new dependency for the file.
- int64_t parentIndex = context.dependencies.length();
- context.dependencies.push(Dependency(absolutePath, extension));
- // Summarize the file's content.
- analyzeFile(context.dependencies[parentIndex], absolutePath, extension);
- // Continue analyzing recursively into the file's dependencies.
- for (int64_t i = 0; i < context.dependencies[parentIndex].includes.length(); i++) {
- analyzeFromFile(context, context.dependencies[parentIndex].includes[i].path);
- }
- for (int64_t l = 0; l < context.dependencies[parentIndex].links.length(); l++) {
- analyzeFromFile(context, context.dependencies[parentIndex].links[l].path);
- }
- }
- }
- static void debugPrintDependencyList(const List<Connection> &connnections, const ReadableString verb) {
- for (int64_t c = 0; c < connnections.length(); c++) {
- int64_t lineNumber = connnections[c].lineNumber;
- if (lineNumber != -1) {
- printText(U" @", lineNumber, U"\t");
- } else {
- printText(U" \t");
- }
- printText(U" ", verb, U" ", file_getPathlessName(connnections[c].path), U"\n");
- }
- }
- void printDependencies(ProjectContext &context) {
- for (int64_t d = 0; d < context.dependencies.length(); d++) {
- printText(U"* ", file_getPathlessName(context.dependencies[d].path), U"\n");
- debugPrintDependencyList(context.dependencies[d].includes, U"including");
- debugPrintDependencyList(context.dependencies[d].links, U"linking");
- }
- }
- static void traverserHeaderChecksums(ProjectContext &context, uint64_t &target, int64_t dependencyIndex) {
- // Use checksums from headers
- for (int64_t h = 0; h < context.dependencies[dependencyIndex].includes.length(); h++) {
- int64_t includedIndex = context.dependencies[dependencyIndex].includes[h].dependencyIndex;
- if (!context.dependencies[includedIndex].visited) {
- // Bitwise exclusive or is both order independent and entropy preserving for non-repeated content.
- target = target ^ context.dependencies[includedIndex].contentChecksum;
- // Just have to make sure that the same checksum is not used twice.
- context.dependencies[includedIndex].visited = true;
- // Use checksums from headers recursively
- traverserHeaderChecksums(context, target, includedIndex);
- }
- }
- }
- static uint64_t getCombinedChecksum(ProjectContext &context, int64_t dependencyIndex) {
- for (int64_t d = 0; d < context.dependencies.length(); d++) {
- context.dependencies[d].visited = false;
- }
- context.dependencies[dependencyIndex].visited = true;
- uint64_t result = context.dependencies[dependencyIndex].contentChecksum;
- traverserHeaderChecksums(context, result, dependencyIndex);
- return result;
- }
- static int64_t findObject(SessionContext &source, uint64_t identityChecksum) {
- for (int64_t o = 0; o < source.sourceObjects.length(); o++) {
- if (source.sourceObjects[o].identityChecksum == identityChecksum) {
- return o;
- }
- }
- return -1;
- }
- void gatherBuildInstructions(SessionContext &output, ProjectContext &context, Machine &settings, ReadableString programPath) {
- validateSettings(settings, string_combine(U"in settings at the beginning of gatherBuildInstructions, for ", programPath, U"\n"));
- // The compiler is often a global alias, so the user must supply either an alias or an absolute path.
- ReadableString compilerName = getFlag(settings, U"Compiler", U"g++"); // Assume g++ as the compiler if not specified.
- ReadableString compileFrom = getFlag(settings, U"CompileFrom", U"");
- // Check if the build system was asked to run the compiler from a specific folder.
- bool changePath = (string_length(compileFrom) > 0);
- if (changePath) {
- printText(U"Using ", compilerName, U" as the compiler executed from ", compileFrom, U".\n");
- } else {
- printText(U"Using ", compilerName, U" as the compiler from the current directory.\n");
- }
- // TODO: Warn if -DNDEBUG, -DDEBUG, or optimization levels are given directly.
- // Using the variables instead is both more flexible by accepting input arguments
- // and keeping the same format to better reuse compiled objects.
- if (getFlagAsInteger(settings, U"Debug")) {
- printText(U"Building with debug mode.\n");
- settings.compilerFlags.push(U"-DDEBUG");
- } else {
- printText(U"Building with release mode.\n");
- settings.compilerFlags.push(U"-DNDEBUG");
- }
- if (getFlagAsInteger(settings, U"StaticRuntime")) {
- if (getFlagAsInteger(settings, U"Windows")) {
- printText(U"Building with static runtime. Your application's binary will be bigger but can run without needing any installer.\n");
- settings.compilerFlags.push(U"-static");
- settings.compilerFlags.push(U"-static-libgcc");
- settings.compilerFlags.push(U"-static-libstdc++");
- settings.linkerFlags.push(U"-static");
- settings.linkerFlags.push(U"-static-libgcc");
- settings.linkerFlags.push(U"-static-libstdc++");
- } else {
- printText(U"The target platform does not support static linking of runtime. But don't worry about bundling any runtimes, because it comes with most of the Posix compliant operating systems.\n");
- }
- } else {
- printText(U"Building with dynamic runtime. Don't forget to bundle the C and C++ runtimes for systems that don't have it pre-installed.\n");
- }
- ReadableString optimizationLevel = getFlag(settings, U"Optimization", U"2");
- printText(U"Building with optimization level ", optimizationLevel, U".\n");
- settings.compilerFlags.push(string_combine(U"-O", optimizationLevel));
- validateSettings(settings, string_combine(U"in settings after adding flags from settings in gatherBuildInstructions, for ", programPath, U"\n"));
- // Convert lists of linker and compiler flags into strings.
- // TODO: Give a warning if two contradictory flags are used, such as optimization levels and language versions.
- // TODO: Make sure that no spaces are inside of the flags, because that can mess up detection of pre-existing and contradictory arguments.
- // TODO: Use groups of compiler flags, so that they can be generated in the last step.
- // This would allow calling the compiler directly when given a folder path for temporary files instead of a script path.
- String generatedCompilerFlags;
- for (int64_t i = 0; i < settings.compilerFlags.length(); i++) {
- printText(U"Build script gave compiler flag:", settings.compilerFlags[i], U"\n");
- string_append(generatedCompilerFlags, U" ", settings.compilerFlags[i]);
- }
- String linkerFlags;
- for (int64_t i = 0; i < settings.linkerFlags.length(); i++) {
- printText(U"Build script gave linker flag:", settings.linkerFlags[i], U"\n");
- string_append(linkerFlags, settings.linkerFlags[i]);
- }
- printText(U"Generating build instructions for ", programPath, U" using settings:\n");
- printText(U" Compiler flags:", generatedCompilerFlags, U"\n");
- printText(U" Linker flags:", linkerFlags, U"\n");
- for (int64_t v = 0; v < settings.variables.length(); v++) {
- printText(U" * ", settings.variables[v].key, U" = ", settings.variables[v].value);
- if (settings.variables[v].inherited) {
- printText(U" (inherited input)");
- }
- printText(U"\n");
- }
- printText(U"Listing source files to compile in the current session.\n");
- // The current project's global indices to objects shared between all projects being built during the session.
- List<int64_t> sourceObjectIndices;
- bool hasSourceCode = false;
- for (int64_t d = 0; d < context.dependencies.length(); d++) {
- Extension extension = context.dependencies[d].extension;
- if (extension == Extension::C || extension == Extension::Cpp) {
- // Dependency paths are already absolute from the recursive search.
- String sourcePath = context.dependencies[d].path;
- String identity = string_combine(sourcePath, generatedCompilerFlags);
- uint64_t identityChecksum = checksum(identity);
- int64_t previousIndex = findObject(output, identityChecksum);
- if (previousIndex == -1) {
- // Content checksums were created while scanning for source code, so now we just combine each source file's content checksum with all its headers to get the combined checksum.
- // The combined checksum represents the state after all headers are included recursively and given as input for compilation unit generating an object.
- uint64_t combinedChecksum = getCombinedChecksum(context, d);
- String objectPath = file_combinePaths(output.tempPath, string_combine(U"dfpsr_", identityChecksum, U"_", combinedChecksum, U".o"));
- sourceObjectIndices.push(output.sourceObjects.length());
- output.sourceObjects.pushConstruct(identityChecksum, combinedChecksum, sourcePath, objectPath, settings.compilerFlags, compilerName, compileFrom);
- } else {
- // Link to this pre-existing source file.
- sourceObjectIndices.push(previousIndex);
- }
- hasSourceCode = true;
- }
- }
- if (hasSourceCode) {
- printText(U"Listing target executable ", programPath, U" in the current session.\n");
- bool executeResult = getFlagAsInteger(settings, U"Supressed") == 0;
- output.linkerSteps.pushConstruct(compilerName, compileFrom, programPath, settings.linkerFlags, sourceObjectIndices, executeResult);
- } else {
- printText(U"Failed to find any source code to compile when building ", programPath, U".\n");
- }
- validateSettings(settings, string_combine(U"in settings at the end of gatherBuildInstructions, for ", programPath, U"\n"));
- }
- static void crawlSource(ProjectContext &context, ReadableString absolutePath) {
- EntryType pathType = file_getEntryType(absolutePath);
- if (pathType == EntryType::File) {
- printText(U"Crawling for source from ", absolutePath, U".\n");
- analyzeFromFile(context, absolutePath);
- } else if (pathType == EntryType::Folder) {
- printText(U"Crawling was given the folder ", absolutePath, U" but a source file was expected!\n");
- } else if (pathType == EntryType::SymbolicLink) {
- // Symbolic links can point to both files and folder, so we need to follow it and find out what it really is.
- crawlSource(context, file_followSymbolicLink(absolutePath));
- }
- }
- static List<String> initializedProjects;
- static void buildProjectFromSettings(SessionContext &output, const ReadableString &path, Machine &settings) {
- printText(U"Building project at ", path, U"\n");
- // Check if this project has begun building previously during this session.
- String absolutePath = file_getAbsolutePath(path);
- for (int64_t p = 0; p < initializedProjects.length(); p++) {
- if (string_caseInsensitiveMatch(absolutePath, initializedProjects[p])) {
- throwError(U"Found duplicate requests to build from the same initial script ", absolutePath, U" which could cause non-determinism if different arguments are given to each!\n");
- return;
- }
- }
- // Remember that building of this project has started.
- initializedProjects.push(absolutePath);
- ProjectContext context;
- // Find out where things are located.
- String projectPath = file_getAbsoluteParentFolder(path);
- // Get the project's name.
- String projectName = file_getPathlessName(file_getExtensionless(path));
- // If no application path is given, the new executable will be named after the project and placed in the same folder.
- String fullProgramPath = getFlag(settings, U"ProgramPath", projectName);
- if (string_length(output.executableExtension) > 0) {
- string_append(fullProgramPath, output.executableExtension);
- }
- // Interpret ProgramPath relative to the project path.
- fullProgramPath = file_getTheoreticalAbsolutePath(fullProgramPath, projectPath);
-
- // Build projects from files. (used for running many tests)
- for (int64_t b = 0; b < settings.projectFromSourceFilenames.length(); b++) {
- buildFromFile(output, settings.projectFromSourceFilenames[b], settings.projectFromSourceSettings[b]);
- }
-
- // Build other projects. (used for compiling programs that the main program should call)
- for (int64_t b = 0; b < settings.otherProjectPaths.length(); b++) {
- buildFromFolder(output, settings.otherProjectPaths[b], settings.otherProjectSettings[b]);
- }
- validateSettings(settings, string_combine(U"in settings after building other projects in buildProject, for ", path, U"\n"));
- // If the SkipIfBinaryExists flag is given, we will abort as soon as we have handled its external BuildProjects requests and confirmed that the application exists.
- if (getFlagAsInteger(settings, U"SkipIfBinaryExists") && file_getEntryType(fullProgramPath) == EntryType::File) {
- // SkipIfBinaryExists was active and the binary exists, so abort here to avoid redundant work.
- printText(U"Skipping build of ", path, U" because the SkipIfBinaryExists flag was given and ", fullProgramPath, U" was found.\n");
- return;
- }
- // Once we know where the binary is and that it should be built, we can start searching for source code.
- for (int64_t o = 0; o < settings.crawlOrigins.length(); o++) {
- crawlSource(context, settings.crawlOrigins[o]);
- }
- validateSettings(settings, string_combine(U"in settings after crawling source in buildProject, for ", path, U"\n"));
- // Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices.
- resolveDependencies(context);
- if (getFlagAsInteger(settings, U"ListDependencies")) {
- printDependencies(context);
- }
- gatherBuildInstructions(output, context, settings, fullProgramPath);
- validateSettings(settings, string_combine(U"in settings after gathering build instructions in buildProject, for ", path, U"\n"));
- }
- // Using a project file path and input arguments.
- void buildProject(SessionContext &output, ReadableString projectFilePath, Machine &sharedSettings) {
- // Inherit external settings.
- Machine settings(file_getPathlessName(projectFilePath));
- inheritMachine(settings, sharedSettings);
- validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", projectFilePath, U"\n"));
- // Evaluate the project's script.
- printText(U"Executing project file from ", projectFilePath, U".\n");
- evaluateScript(settings, projectFilePath);
- validateSettings(settings, string_combine(U"in settings after evaluateScript in buildProject, for ", projectFilePath, U"\n"));
- // Complete the project.
- buildProjectFromSettings(output, projectFilePath, settings);
- }
- // Using a folder path and input arguments for all projects.
- void buildProjects(SessionContext &output, ReadableString projectFolderPath, Machine &sharedSettings) {
- printText(U"Building all projects in ", projectFolderPath, U"\n");
- file_getFolderContent(projectFolderPath, [&sharedSettings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
- if (entryType == EntryType::Folder) {
- buildProjects(output, entryPath, sharedSettings);
- } else if (entryType == EntryType::File) {
- ReadableString extension = file_getExtension(entryName);
- if (string_caseInsensitiveMatch(extension, U"DSRPROJ")) {
- buildProject(output, entryPath, sharedSettings);
- }
- }
- });
- }
- void buildFromFolder(SessionContext &output, ReadableString projectPath, Machine &sharedSettings) {
- EntryType entryType = file_getEntryType(projectPath);
- printText(U"Building anything at ", projectPath, U" which is ", entryType, U"\n");
- if (entryType == EntryType::File) {
- String extension = string_upperCase(file_getExtension(projectPath));
- if (!string_match(extension, U"DSRPROJ")) {
- printText(U"Can't use the Build keyword with a file that is not a project!\n");
- } else {
- // Build the given project
- buildProject(output, projectPath, sharedSettings);
- }
- } else if (entryType == EntryType::Folder) {
- buildProjects(output, projectPath, sharedSettings);
- }
- }
- void buildFromFile(SessionContext &output, ReadableString mainPath, Machine &sharedSettings) {
- // Inherit settings, flags and dependencies from the parent, because they do not exist in single source files.
- Machine settings(file_getPathlessName(mainPath));
- cloneMachine(settings, sharedSettings);
- ReadableString extension = file_getExtension(mainPath);
- if (!(string_caseInsensitiveMatch(extension, U"c") || string_caseInsensitiveMatch(extension, U"cpp"))) {
- throwError(U"Creating projects from source files is currently only supported for *.c and *.cpp, but the extension was '", extension, U"'.");
- }
- // Crawl from the selected file to discover direct dependencies.
- settings.crawlOrigins.push(mainPath);
- // Check that settings are okay.
- validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", mainPath, U"\n"));
- // Create the project to save as a script or build using direct calls to the compiler.
- buildProjectFromSettings(output, mainPath, settings);
- }
|