Enabled relative includes in glslc.
glslc will now by default try and resolve #included files relative to
the current file first, only after that fails will it try to include
files based on the -I parameters.
diff --git a/glslc/README.asciidoc b/glslc/README.asciidoc
index 5ed1f91..1e47d7b 100644
--- a/glslc/README.asciidoc
+++ b/glslc/README.asciidoc
@@ -210,7 +210,10 @@
`-I` adds the specified directory to the search path for include files.
-WARNING: Implementation is not complete yet.
+Only `""` style includes are permitted. When searching for included files, first
+the directory of the current file is attempted. If this fails each directory
+supplied via `-I` on the command line is searched in the given order for the
+file.
=== Code Generation Options
@@ -386,3 +389,11 @@
The `GL_GOOGLE_cpp_style_line_directive` extension is implicitly turned on by
the `GL_GOOGLE_include_directive` extension.
+
+It is invalid to have any '\' or '/' characters in any filename provided to
+an `#include` directive. The presence of any such characters may lead to
+unexpected behavior on different systems.
+
+Furthermore it is not possible to escape any characters in an `#include`
+directive and as such, any special characters that would have to be escaped in
+C may not show up in file names either.
diff --git a/glslc/src/file_includer.cc b/glslc/src/file_includer.cc
index 8af500f..ea42e81 100644
--- a/glslc/src/file_includer.cc
+++ b/glslc/src/file_includer.cc
@@ -25,13 +25,16 @@
return new shaderc_include_result{"", 0, message, strlen(message)};
}
-shaderc_include_result* FileIncluder::GetInclude(const char* requested_source,
- shaderc_include_type,
- const char*, size_t) {
- // TODO(dneto): Be sensitive to requesting_source
- // We don't actually care about the include type.
+shaderc_include_result* FileIncluder::GetInclude(
+ const char* requested_source, shaderc_include_type include_type,
+ const char* requesting_source, size_t) {
+
const std::string full_path =
- file_finder_.FindReadableFilepath(requested_source);
+ (include_type == shaderc_include_type_relative)
+ ? file_finder_.FindRelativeReadableFilepath(requesting_source,
+ requested_source)
+ : file_finder_.FindReadableFilepath(requested_source);
+
if (full_path.empty())
return MakeErrorIncludeResult("Cannot find or open include file.");
diff --git a/glslc/src/main.cc b/glslc/src/main.cc
index 5f84c87..9482949 100644
--- a/glslc/src/main.cc
+++ b/glslc/src/main.cc
@@ -112,8 +112,6 @@
bool success = true;
bool has_stdin_input = false;
- compiler.AddIncludeDirectory("");
-
for (int i = 1; i < argc; ++i) {
const string_piece arg = argv[i];
if (arg == "--help") {
diff --git a/glslc/test/glslc_test_framework.py b/glslc/test/glslc_test_framework.py
index e6f2f52..6a2a3b9 100755
--- a/glslc/test/glslc_test_framework.py
+++ b/glslc/test/glslc_test_framework.py
@@ -269,6 +269,10 @@
setattr(
self.test, expectation_name,
''.join(expanded_expections))
+ elif isinstance(expectation, PlaceHolder):
+ setattr(self.test, expectation_name,
+ expectation.instantiate_for_expectation(self))
+
def tearDown(self):
"""Removes the directory if we were not instructed to do otherwise."""
diff --git a/glslc/test/include.py b/glslc/test/include.py
index dfb1aa0..a965032 100644
--- a/glslc/test/include.py
+++ b/glslc/test/include.py
@@ -14,6 +14,7 @@
import expect
from glslc_test_framework import inside_glslc_testsuite
+from placeholder import SpecializedString
from environment import File, Directory
@@ -306,3 +307,255 @@
"b.glsl:1: error: '#version' : must occur first in shader\n",
'1 error generated.\n'
]
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeInclude(expect.StdoutMatch):
+ """Tests #including a relative sibling."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "foo/b.glsl"\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "c.glsl"\ncontent b\n'),
+ File('c.glsl', 'content c\n')
+ ])])
+
+ glslc_args = ['-E', 'a.vert']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "foo/b.glsl"
+#line 0 "foo/c.glsl"
+ content c
+#line 1 "foo/b.glsl"
+ content b
+#line 3 "a.vert"
+
+"""
+
+@inside_glslc_testsuite('Include')
+class VerifyNestedRelativeInclude(expect.StdoutMatch):
+ """Tests #including a relative child file."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "foo/b.glsl"\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "bar/c.glsl"\ncontent b\n'),
+ Directory('bar', [
+ File('c.glsl', 'content c\n')
+ ])
+ ])
+ ])
+
+ glslc_args = ['-E', 'a.vert']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "foo/b.glsl"
+#line 0 "foo/bar/c.glsl"
+ content c
+#line 1 "foo/b.glsl"
+ content b
+#line 3 "a.vert"
+
+"""
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeIncludeWithDashI(expect.StdoutMatch):
+ """Tests #including a relative file from a -I directory."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "bar/b.glsl"\n'),
+ Directory('foo', [
+ Directory('bar', [
+ File('b.glsl', '#include "c.glsl"\ncontent b\n'),
+ ]),
+ File('c.glsl', 'content c\n')
+ ])
+ ])
+
+ glslc_args = ['-E', 'a.vert', '-Ifoo']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "foo/bar/b.glsl"
+#line 0 "foo/c.glsl"
+ content c
+#line 1 "foo/bar/b.glsl"
+ content b
+#line 3 "a.vert"
+
+"""
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeOverridesDashI(expect.StdoutMatch):
+ """Tests that relative includes override -I parameters."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "b.glsl"\n'),
+ File('b.glsl', 'content base_b\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "c.glsl"\ncontent b\n'),
+ File('c.glsl', 'content c\n')
+ ])
+ ])
+
+ glslc_args = ['-E', 'a.vert', '-Ifoo']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "b.glsl"
+ content base_b
+#line 3 "a.vert"
+
+"""
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeParent(expect.StdoutMatch):
+ """Tests #including a parent file."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "b.glsl"\n'),
+ File('c.glsl', 'content c\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "../c.glsl"\ncontent b\n')
+ ])
+ ])
+
+ glslc_args = ['-E', 'a.vert', '-Ifoo']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "foo/b.glsl"
+#line 0 "foo/../c.glsl"
+ content c
+#line 1 "foo/b.glsl"
+ content b
+#line 3 "a.vert"
+
+"""
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeNeighbourDirectory(expect.StdoutMatch):
+ """Tests #including a relative file in a neighbour directory."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "foo/b.glsl"\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "../bar/c.glsl"\ncontent b\n')
+ ]),
+ Directory('bar', [
+ File('c.glsl', 'content c\n')
+ ])
+ ])
+
+ glslc_args = ['-E', 'a.vert']
+
+ expected_stdout = \
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "foo/b.glsl"
+#line 0 "foo/../bar/c.glsl"
+ content c
+#line 1 "foo/b.glsl"
+ content b
+#line 3 "a.vert"
+
+"""
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeOnlyToSelf(expect.ErrorMessage):
+ """Tests that a relative includes are only relative to yourself."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "foo/b.glsl"\n'),
+ File('c.glsl', 'content c\n'),
+ Directory('foo', [
+ File('b.glsl', '#include "c.glsl"\ncontent b\n')
+ ]),
+ ])
+
+ glslc_args = ['-E', 'a.vert']
+
+ expected_error = [
+ "foo/b.glsl:1: error: '#include' : Cannot find or open include file.\n",
+ '1 error generated.\n'
+ ]
+
+
+@inside_glslc_testsuite('Include')
+class VerifyRelativeFromAbsolutePath(expect.StdoutMatch):
+ """Tests that absolute files can relatively include."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "b.glsl"\n'),
+ File('b.glsl', 'content b\n')
+ ])
+
+ glslc_args = ['-E', SpecializedString('$directory/a.vert')]
+
+ expected_stdout = SpecializedString(
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "$directory/a.vert"
+
+content a
+#line 0 "$directory/b.glsl"
+ content b
+#line 3 "$directory/a.vert"
+
+""")
+
+
+@inside_glslc_testsuite('Include')
+class VerifyDashIAbsolutePath(expect.StdoutMatch):
+ """Tests that -I parameters can be absolute paths."""
+
+ environment = Directory('.', [
+ File('a.vert', '#version 140\ncontent a\n#include "b.glsl"\n'),
+ Directory('foo', {
+ File('b.glsl', 'content b\n')
+ })
+ ])
+
+ glslc_args = ['-E', 'a.vert', '-I', SpecializedString('$directory/foo')]
+
+ expected_stdout = SpecializedString(
+"""#version 140
+#extension GL_GOOGLE_include_directive : enable
+#line 0 "a.vert"
+
+content a
+#line 0 "$directory/foo/b.glsl"
+ content b
+#line 3 "a.vert"
+
+""")
diff --git a/glslc/test/placeholder.py b/glslc/test/placeholder.py
index 2cb8e50..9c5bcfc 100644
--- a/glslc/test/placeholder.py
+++ b/glslc/test/placeholder.py
@@ -22,6 +22,7 @@
"""
import os
import tempfile
+from string import Template
class PlaceHolderException(Exception):
@@ -115,3 +116,24 @@
def instantiate_for_expectation(self, testcase):
return os.path.join(testcase.directory, self.filename)
+
+
+class SpecializedString(PlaceHolder):
+ """Returns a string that has been specialized based on TestCase.
+
+ The string is specialized by expanding it as a string.Template
+ with all of the specialization being done with each $param replaced
+ by the associated member on TestCase.
+ """
+
+ def __init__(self, filename):
+ assert isinstance(filename, str)
+ assert filename != ''
+ self.filename = filename
+
+ def instantiate_for_glslc_args(self, testcase):
+ return Template(self.filename).substitute(vars(testcase))
+
+ def instantiate_for_expectation(self, testcase):
+ return Template(self.filename).substitute(vars(testcase))
+
diff --git a/libshaderc_util/include/libshaderc_util/counting_includer.h b/libshaderc_util/include/libshaderc_util/counting_includer.h
index c583e20..d341ad1 100644
--- a/libshaderc_util/include/libshaderc_util/counting_includer.h
+++ b/libshaderc_util/include/libshaderc_util/counting_includer.h
@@ -43,7 +43,8 @@
const char* requesting_source,
size_t include_depth) final {
++num_include_directives_;
- return include_delegate(requested_source, type, requesting_source, include_depth);
+ return include_delegate(requested_source, type, requesting_source,
+ include_depth);
}
// Releases the given IncludeResult.
diff --git a/libshaderc_util/include/libshaderc_util/file_finder.h b/libshaderc_util/include/libshaderc_util/file_finder.h
index 8aec788..2c1c9d1 100644
--- a/libshaderc_util/include/libshaderc_util/file_finder.h
+++ b/libshaderc_util/include/libshaderc_util/file_finder.h
@@ -33,14 +33,18 @@
// If a search_path() element is non-empty and not ending in a slash, then a
// slash is inserted between it and filename before its search attempt. An
// empty string in search_path() means that the filename is tried as-is.
- //
- // Usage advice: when searching #include files, you almost certainly want ""
- // to be the first element in search_path(). That way both relative and
- // absolute filenames will work as expected. Note that a "." entry on the
- // search path may be prepended to an absolute filename (eg, "/a/b/c") to
- // create a relative result (eg, ".//a/b/c").
std::string FindReadableFilepath(const std::string& filename) const;
+ // Searches for a read-openable file based on filename, which must be
+ // non-empty. The search is first attempted as a path relative to
+ // the requesting_file parameter. If no file is found relative to the
+ // requesting_file then this acts as FindReadableFilepath does. If
+ // requesting_file does not contain a '/' or a '\' character then it is
+ // assumed to be a filename and the request will be relative to the
+ // current directory.
+ std::string FindRelativeReadableFilepath(const std::string& requesting_file,
+ const std::string& filename) const;
+
// Search path for Find(). Users may add/remove elements as desired.
std::vector<std::string>& search_path() { return search_path_; }
diff --git a/libshaderc_util/src/file_finder.cc b/libshaderc_util/src/file_finder.cc
index df926b6..d25c611 100644
--- a/libshaderc_util/src/file_finder.cc
+++ b/libshaderc_util/src/file_finder.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include "libshaderc_util/file_finder.h"
+#include "libshaderc_util/string_piece.h"
#include <cassert>
#include <fstream>
@@ -21,7 +22,7 @@
namespace {
// Returns "" if path is empty or ends in '/'. Otherwise, returns "/".
-std::string MaybeSlash(const std::string& path) {
+std::string MaybeSlash(const shaderc_util::string_piece& path) {
return (path.empty() || path.back() == '/') ? "" : "/";
}
@@ -42,4 +43,29 @@
return "";
}
+std::string FileFinder::FindRelativeReadableFilepath(
+ const std::string& requesting_file, const std::string& filename) const {
+ assert(!filename.empty());
+
+ string_piece dir_name(requesting_file);
+
+ size_t last_slash = requesting_file.find_last_of("/\\");
+ if (last_slash != std::string::npos) {
+ dir_name = string_piece(requesting_file.c_str(),
+ requesting_file.c_str() + last_slash);
+ }
+
+ if (dir_name.size() == requesting_file.size()) {
+ dir_name.clear();
+ }
+
+ static const auto for_reading = std::ios_base::in;
+ std::filebuf opener;
+ const std::string relative_filename =
+ dir_name.str() + MaybeSlash(dir_name) + filename;
+ if (opener.open(relative_filename, for_reading)) return relative_filename;
+
+ return FindReadableFilepath(filename);
+}
+
} // namespace shaderc_util