diff --git a/gn/compile_processors.py b/gn/compile_processors.py index a8b1c4c5db..3e6cd4551c 100755 --- a/gn/compile_processors.py +++ b/gn/compile_processors.py @@ -8,6 +8,7 @@ import os import subprocess import sys +import tempfile skslc = sys.argv[1] clangFormat = sys.argv[2] @@ -15,14 +16,14 @@ fetchClangFormat = sys.argv[3] processors = sys.argv[4:] exeSuffix = '.exe' if sys.platform.startswith('win') else ''; -skslcArgs = [skslc] -clangFormatArgs = [clangFormat, "--sort-includes=false", "-i"] +targets = [] +worklist = tempfile.NamedTemporaryFile(suffix='.worklist') # Fetch clang-format if it's not present already. if not os.path.isfile(clangFormat + exeSuffix): subprocess.check_call([sys.executable, fetchClangFormat]); -# Build argument lists for all the fragment processors that we want to compile. +# Build a worklist of all the fragment processors that we want to compile. for p in processors: noExt, _ = os.path.splitext(p) head, tail = os.path.split(noExt) @@ -30,27 +31,26 @@ for p in processors: if not os.path.isdir(targetDir): os.mkdir(targetDir) target = os.path.join(targetDir, tail) - clangFormatArgs.append(target + ".h") - clangFormatArgs.append(target + ".cpp") - skslcArgs.append("--"); - skslcArgs.append(p); - skslcArgs.append(target + ".h"); - skslcArgs.append("--"); - skslcArgs.append(p); - skslcArgs.append(target + ".cpp"); + targets.append(target + ".h") + targets.append(target + ".cpp") -# Invoke skslc on every target that needs to be compiled. + worklist.write(p + "\n") + worklist.write(target + ".h\n\n") + worklist.write(p + "\n") + worklist.write(target + ".cpp\n\n") + +# Invoke skslc, passing in the worklist. +worklist.close() try: - output = subprocess.check_output(skslcArgs, stderr=subprocess.STDOUT) + output = subprocess.check_output([skslc, worklist.name], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as err: print("### skslc error:\n") print("\n".join(err.output.splitlines())) - sys.exit(err.returncode) # Invoke clang-format on every generated target. try: - output = subprocess.check_output(clangFormatArgs, stderr=subprocess.STDOUT) + output = subprocess.check_output([clangFormat, "--sort-includes=false", "-i"] + targets, + stderr=subprocess.STDOUT) except subprocess.CalledProcessError as err: print("### clang-format error:\n") print("\n".join(err.output.splitlines())) - sys.exit(err.returncode) diff --git a/gn/compile_sksl_tests.py b/gn/compile_sksl_tests.py index 92abd57d9f..4b106b2d7c 100755 --- a/gn/compile_sksl_tests.py +++ b/gn/compile_sksl_tests.py @@ -8,6 +8,7 @@ import os import subprocess import sys +import tempfile skslc = sys.argv[1] lang = sys.argv[2] @@ -23,8 +24,8 @@ def makeEmptyFile(path): if settings != "--settings" and settings != "--nosettings": sys.exit("### Expected --settings or --nosettings, got " + settings) -skslcArgs = [skslc] targets = [] +worklist = tempfile.NamedTemporaryFile(suffix='.worklist') # Convert the list of command-line inputs into a worklist file sfor skslc. for input in inputs: @@ -41,36 +42,31 @@ for input in inputs: targets.append(target) if lang == "--fp": - skslcArgs.append("--") - skslcArgs.append(input) - skslcArgs.append(target + ".cpp") - skslcArgs.append(settings) - skslcArgs.append("--") - skslcArgs.append(input) - skslcArgs.append(target + ".h") - skslcArgs.append(settings) + worklist.write(input + "\n") + worklist.write(target + ".cpp\n") + worklist.write(settings + "\n\n") + worklist.write(input + "\n") + worklist.write(target + ".h\n") + worklist.write(settings + "\n\n") elif lang == "--glsl": - skslcArgs.append("--") - skslcArgs.append(input) - skslcArgs.append(target + ".glsl") - skslcArgs.append(settings) + worklist.write(input + "\n") + worklist.write(target + ".glsl\n") + worklist.write(settings + "\n\n") elif lang == "--metal": - skslcArgs.append("--") - skslcArgs.append(input) - skslcArgs.append(target + ".metal") - skslcArgs.append(settings) + worklist.write(input + "\n") + worklist.write(target + ".metal\n") + worklist.write(settings + "\n\n") else: sys.exit("### Expected one of: --fp --glsl --metal, got " + lang) -# Invoke skslc on every target that needs to be compiled. +# Invoke skslc, passing in the worklist. +worklist.close() try: - output = subprocess.check_output(skslcArgs, stderr=subprocess.STDOUT) + output = subprocess.check_output([skslc, worklist.name], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: - if err.returncode != 1: - print("### skslc error:\n") - print("\n".join(err.output.splitlines())) - sys.exit(err.returncode) - pass # Compile errors (exit code 1) are expected and normal in test code + print("### skslc error:\n") + print("\n".join(err.output.splitlines())) # A special case cleanup pass, just for CPP and H files: if either one of these files starts with # `### Compilation failed`, its sibling should be replaced by an empty file. This improves clarity diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp index 5ee4d3696b..0f44e4fe7b 100644 --- a/src/sksl/SkSLMain.cpp +++ b/src/sksl/SkSLMain.cpp @@ -33,13 +33,6 @@ namespace SkOpts { decltype(hash_fn) hash_fn = skslc_standalone::hash_fn; } -enum class ResultCode { - kSuccess = 0, - kCompileError = 1, - kInputError = 2, - kOutputError = 3, -}; - // Given the path to a file (e.g. src/gpu/effects/GrFooFragmentProcessor.fp) and the expected // filename prefix and suffix (e.g. "Gr" and ".fp"), returns the "base name" of the // file (in this case, 'FooFragmentProcessor'). If no match, returns the empty string. @@ -209,7 +202,8 @@ static bool detect_shader_settings(const SkSL::String& text, * Displays a usage banner; used when the command line arguments don't make sense. */ static void show_usage() { - printf("usage: skslc -- -- ...\n" + printf("usage: skslc \n" + " skslc \n" "\n" "Allowed flags:\n" "--settings: honor embedded /*#pragma settings*/ comments.\n" @@ -219,7 +213,7 @@ static void show_usage() { /** * Handle a single input. */ -ResultCode processCommand(std::vector& args) { +int processCommand(std::vector& args, bool writeErrorsToOutputFile) { bool honorSettings = true; if (args.size() == 4) { // Handle four-argument case: `skslc in.sksl out.glsl --settings` @@ -231,11 +225,11 @@ ResultCode processCommand(std::vector& args) { } else { printf("unrecognized flag: %s\n\n", settingsArg.c_str()); show_usage(); - return ResultCode::kInputError; + return 1; } } else if (args.size() != 3) { show_usage(); - return ResultCode::kInputError; + return 1; } SkSL::Program::Kind kind; @@ -253,7 +247,7 @@ ResultCode processCommand(std::vector& args) { } else { printf("input filename must end in '.vert', '.frag', '.geom', '.fp', '.stage', or " "'.sksl'\n"); - return ResultCode::kInputError; + return 1; } std::ifstream in(inputPath); @@ -261,27 +255,30 @@ ResultCode processCommand(std::vector& args) { std::istreambuf_iterator()); if (in.rdstate()) { printf("error reading '%s'\n", inputPath.c_str()); - return ResultCode::kInputError; + return 2; } SkSL::Program::Settings settings; const SkSL::ShaderCapsClass* caps = &SkSL::standaloneCaps; if (honorSettings) { if (!detect_shader_settings(text, &settings, &caps)) { - return ResultCode::kInputError; + return 3; } } const SkSL::String& outputPath = args[2]; auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) { - // Overwrite the compiler output, if any, with an error message. - out.close(); - SkSL::FileOutputStream errorStream(outputPath); - errorStream.writeText("### Compilation failed:\n\n"); - errorStream.writeText(errorText); - errorStream.close(); - // Also emit the error directly to stdout. - puts(errorText); + if (writeErrorsToOutputFile) { + // Overwrite the compiler output, if any, with an error message. + out.close(); + SkSL::FileOutputStream errorStream(outputPath); + errorStream.writeText("### Compilation failed:\n\n"); + errorStream.writeText(errorText); + errorStream.close(); + } else { + // Emit the error directly to stdout. + puts(errorText); + } }; if (outputPath.endsWith(".spirv")) { @@ -289,89 +286,89 @@ ResultCode processCommand(std::vector& args) { SkSL::Compiler compiler(caps); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } std::unique_ptr program = compiler.convertProgram(kind, text, settings); if (!program || !compiler.toSPIRV(*program, out)) { emitCompileError(out, compiler.errorText().c_str()); - return ResultCode::kCompileError; + return 3; } if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else if (outputPath.endsWith(".glsl")) { SkSL::FileOutputStream out(outputPath); SkSL::Compiler compiler(caps); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } std::unique_ptr program = compiler.convertProgram(kind, text, settings); if (!program || !compiler.toGLSL(*program, out)) { emitCompileError(out, compiler.errorText().c_str()); - return ResultCode::kCompileError; + return 3; } if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else if (outputPath.endsWith(".metal")) { SkSL::FileOutputStream out(outputPath); SkSL::Compiler compiler(caps); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } std::unique_ptr program = compiler.convertProgram(kind, text, settings); if (!program || !compiler.toMetal(*program, out)) { emitCompileError(out, compiler.errorText().c_str()); - return ResultCode::kCompileError; + return 3; } if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else if (outputPath.endsWith(".h")) { SkSL::FileOutputStream out(outputPath); SkSL::Compiler compiler(caps, SkSL::Compiler::kPermitInvalidStaticTests_Flag); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } settings.fReplaceSettings = false; std::unique_ptr program = compiler.convertProgram(kind, text, settings); if (!program || !compiler.toH(*program, base_name(inputPath.c_str(), "Gr", ".fp"), out)) { emitCompileError(out, compiler.errorText().c_str()); - return ResultCode::kCompileError; + return 3; } if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else if (outputPath.endsWith(".cpp")) { SkSL::FileOutputStream out(outputPath); SkSL::Compiler compiler(caps, SkSL::Compiler::kPermitInvalidStaticTests_Flag); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } settings.fReplaceSettings = false; std::unique_ptr program = compiler.convertProgram(kind, text, settings); if (!program || !compiler.toCPP(*program, base_name(inputPath.c_str(), "Gr", ".fp"), out)) { emitCompileError(out, compiler.errorText().c_str()); - return ResultCode::kCompileError; + return 3; } if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else if (outputPath.endsWith(".dehydrated.sksl")) { SkSL::FileOutputStream out(outputPath); SkSL::Compiler compiler(caps); if (!out.isValid()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } auto [symbols, elements] = compiler.loadModule( kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()), nullptr); @@ -391,29 +388,43 @@ ResultCode processCommand(std::vector& args) { baseName.c_str(), baseName.c_str()); if (!out.close()) { printf("error writing '%s'\n", outputPath.c_str()); - return ResultCode::kOutputError; + return 4; } } else { printf("expected output filename to end with '.spirv', '.glsl', '.cpp', '.h', or '.metal'"); - return ResultCode::kInputError; // the "output filename" is still an input argument + return 1; } - return ResultCode::kSuccess; + return 0; } -int main(int argc, const char** argv) { - // Search the command line for -- delimiters. When a -- is reached, we process one command. - std::vector args = {argv[0]}; - auto resultCode = ResultCode::kSuccess; - for (int index = 1; index < argc; ++index) { - SkSL::String arg = argv[index]; - if (arg != "--") { +/** + * Processes multiple inputs in a single invocation of skslc. + */ +int processWorklist(const char* worklistPath) { + SkSL::String inputPath(worklistPath); + if (!inputPath.endsWith(".worklist")) { + printf("expected .worklist file, found: %s\n\n", worklistPath); + show_usage(); + return 1; + } + + // The worklist contains one line per argument to pass to skslc. When a blank line is reached, + // those arguments will be passed to `processCommand`. + std::vector args = {"skslc"}; + std::ifstream in(worklistPath); + for (SkSL::String line; std::getline(in, line); ) { + if (in.rdstate()) { + printf("error reading '%s'\n", worklistPath); + return 2; + } + + if (!line.empty()) { // We found an argument. Remember it. - args.push_back(std::move(arg)); + args.push_back(std::move(line)); } else { - // We found a delimiter. If we have any arguments stored up, process them as a command. - if (args.size() > 1) { - ResultCode outcome = processCommand(args); - resultCode = std::max(resultCode, outcome); + // We found a blank line. If we have any arguments stored up, process them as a command. + if (!args.empty()) { + processCommand(args, /*writeErrorsToOutputFile=*/true); // Clear every argument except the first ("skslc"). args.resize(1); @@ -421,14 +432,26 @@ int main(int argc, const char** argv) { } } - // Execute the final command in the batch. + // If the worklist ended with a list of arguments but no blank line, process those now. if (args.size() > 1) { - ResultCode outcome = processCommand(args); - resultCode = std::max(resultCode, outcome); + processCommand(args, /*writeErrorsToOutputFile=*/true); } - // Return the "worst" status we encountered. For our purposes, compilation errors are the least - // serious, because they are expected to occur in unit tests. Other types of errors are not - // expected at all during a build. - return (int) resultCode; + return 0; +} + +int main(int argc, const char** argv) { + if (argc == 2) { + // Worklists are the only two-argument case for skslc, and we don't intend to support + // nested worklists, so we can process them here. + return processWorklist(argv[1]); + } else { + // Process non-worklist inputs. + std::vector args; + for (int index=0; index