Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
performance
Megadep
Commits
15a8e46f
Commit
15a8e46f
authored
Jul 15, 2019
by
Pádraig Ó Conbhuí
Browse files
resolve_include: Add relative pathname resolution
parent
94b5e96d
Changes
3
Hide whitespace changes
Inline
Side-by-side
src/megadep.main.cpp
View file @
15a8e46f
#include
<megadep/filesystem.hpp>
#include
<algorithm>
#include
<array>
#include
<iostream>
#include
<iterator>
#include
<string>
...
...
@@ -8,7 +9,7 @@
#include
<vector>
// List of file suffixes the program will consider
std
::
vector
<
std
::
string
>
suffixes
=
{
constexpr
std
::
array
<
std
::
string
_view
,
8
>
suffixes
=
{
".c"
,
".cc"
,
".cpp"
,
".cxx"
,
".h"
,
".hh"
,
".hpp"
,
".hxx"
,
};
...
...
@@ -30,17 +31,15 @@ int main(int argc, char *argv[]) {
std
::
vector
<
std
::
string
>
include_directories
;
for
(
int
i
=
1
;
i
<
argc
;
i
++
)
{
std
::
string_view
argv_str
=
argv
[
i
];
std
::
string_view
argv_str
=
argv
[
i
];
// NOLINT
if
(
argv_str
.
compare
(
0
,
2
,
"-I"
)
==
0
)
{
// -I for include directory
argv_str
.
remove_prefix
(
2
);
include_directories
.
emplace_back
(
argv_str
);
continue
;
}
else
{
// Regular positional argument for project directory
project_directories
.
emplace_back
(
argv_str
);
continue
;
}
}
...
...
src/megadep/filesystem.cpp
View file @
15a8e46f
...
...
@@ -131,7 +131,7 @@ namespace {
// Remove the filename from the path, leaving the directory.
// This expects a path to a file just strips the path back to the last slash.
std
::
string_view
directory
_from_file_name
(
std
::
string_view
file_name
)
{
std
::
string_view
get_
directory
(
std
::
string_view
file_name
)
{
const
auto
last_slash_position
=
file_name
.
find_last_of
(
'/'
);
// No slash found.
...
...
@@ -142,15 +142,100 @@ std::string_view directory_from_file_name(std::string_view file_name) {
return
file_name
.
substr
(
0
,
last_slash_position
);
}
// Rewrite path in-place removing and ./, /./, or /../ .
// Note: this won't work if the path starts with ../ .
// Returns the new one-past-the-end iterator.
char
*
resolve_relative_path
(
char
*
path_begin
,
char
*
path_end
)
{
// Get the start of the next path entity.
// e.g.
// some/ENTITY/other/stuff
// b r e
// where `b` and `e` denote the `begin` and `end` pointers,
// and `r` denotes the returned pointer.
const
auto
next_entity_start
=
[](
char
*
begin
,
char
*
end
)
{
auto
pos
=
std
::
find
(
begin
,
end
,
'/'
);
if
(
pos
!=
end
)
{
pos
++
;
}
return
pos
;
};
// Get the last entity.
// e.g
// /other/stuff/ENTITY
// b r e
// where `b` and `e` denote the `begin` and `end` pointers,
// and `r` denotes the returned pointer.
const
auto
last_entity_start
=
[](
char
*
begin
,
char
*
end
)
{
if
(
begin
==
end
)
{
return
begin
;
}
for
(
auto
it
=
std
::
prev
(
end
);
it
!=
begin
;
it
--
)
{
if
(
*
it
==
'/'
)
{
return
std
::
next
(
it
);
}
}
return
begin
;
};
char
*
write_pos
=
path_begin
;
for
(
char
*
read_pos
=
path_begin
;
read_pos
<
path_end
;)
{
// Find the start of the next entity, and make a string_view of
// the current entity, including the '/'.
auto
next_pos
=
next_entity_start
(
read_pos
,
path_end
);
std
::
string_view
next_entity
=
std
::
string_view
{
read_pos
,
static_cast
<
size_t
>
(
next_pos
-
read_pos
)};
// Match './' .
// Do nothing, don't copy to write_pos, don't update write_pos.
if
(
next_entity
==
"./"
)
{
read_pos
=
next_pos
;
continue
;
}
// Match '../' .
// Don't copy to write_pos, and rewind write_pos to a previous entity.
if
(
next_entity
==
"../"
)
{
// Only parse ../ if it's not at the start of the string.
if
(
write_pos
!=
path_begin
)
{
auto
prev_pos
=
last_entity_start
(
path_begin
,
std
::
prev
(
write_pos
));
write_pos
=
prev_pos
;
read_pos
=
next_pos
;
continue
;
}
}
// Copy the entity into the write_pos.
// Move write_pos and read_pos along to the next entity.
std
::
copy
(
read_pos
,
next_pos
,
write_pos
);
write_pos
=
std
::
next
(
write_pos
,
(
next_pos
-
read_pos
));
read_pos
=
next_pos
;
}
return
write_pos
;
}
}
// namespace
std
::
string
resolve_include
(
std
::
string_view
include
,
std
::
string_view
source_file
,
const
std
::
vector
<
std
::
string
>
&
search_directories
)
{
// Buffer holding the current filename.
std
::
array
<
char
,
filename_max
>
filename_buffer
{};
assert
(
include
.
size
()
+
1
<
filename_buffer
.
size
());
// filename_buffer length without trailing '\0'.
size_t
filename_buffer_strlen
=
0
;
assert
(
include
.
size
()
+
1
<
filename_buffer
.
size
()
&&
"include is too big for filename_buffer!"
);
// The include name without <, >, ", or '.
std
::
string_view
stripped_include
=
include
.
substr
(
1
,
include
.
size
()
-
2
);
...
...
@@ -159,7 +244,8 @@ resolve_include(std::string_view include, std::string_view source_file,
// This uses filename_buffer as a buffer.
const
auto
build_file_name
=
[
&
](
std
::
string_view
search_directory
)
{
assert
(
search_directory
.
size
()
+
1
+
stripped_include
.
size
()
+
1
<
filename_buffer
.
size
());
filename_buffer
.
size
()
&&
"include + search_directory is too big for filaneme_buffer!"
);
// Add search directory
search_directory
.
copy
(
filename_buffer
.
begin
(),
search_directory
.
size
());
...
...
@@ -175,27 +261,41 @@ resolve_include(std::string_view include, std::string_view source_file,
filename_buffer
[
search_directory
.
size
()
+
1
+
stripped_include
.
size
()]
=
'\0'
;
filename_buffer_strlen
=
search_directory
.
size
()
+
1
+
stripped_include
.
size
();
return
filename_buffer
.
data
();
};
// Check the file name exists on the disk / is accessible.
const
auto
file_exists
=
[
&
](
const
char
*
file_name
)
{
return
access
(
file_name
,
F_OK
)
==
0
;
const
auto
file_exists
=
[
&
]
{
return
access
(
filename_buffer
.
data
(),
F_OK
)
==
0
;
};
const
auto
resolve_filename
=
[
&
]()
{
auto
new_end
=
resolve_relative_path
(
filename_buffer
.
begin
(),
filename_buffer
.
begin
()
+
filename_buffer_strlen
+
1
);
filename_buffer_strlen
=
new_end
-
filename_buffer
.
begin
()
-
1
;
};
// For #include "filename.hpp", look in the source file directory first.
switch
(
include
[
0
])
{
case
'"'
:
// fallthrough
case
'\''
:
if
(
file_exists
(
build_file_name
(
directory_from_file_name
(
source_file
))))
{
return
filename_buffer
.
data
();
build_file_name
(
get_directory
(
source_file
));
if
(
file_exists
())
{
resolve_filename
();
return
{
filename_buffer
.
data
(),
filename_buffer_strlen
};
}
}
// For everything else, look in the supplied include directories
for
(
const
auto
&
search_directory
:
search_directories
)
{
if
(
file_exists
(
build_file_name
(
search_directory
)))
{
return
filename_buffer
.
data
();
build_file_name
(
search_directory
);
if
(
file_exists
())
{
resolve_filename
();
return
{
filename_buffer
.
data
(),
filename_buffer_strlen
};
}
}
...
...
@@ -206,7 +306,7 @@ resolve_include(std::string_view include, std::string_view source_file,
filename_buffer
[
stripped_include
.
size
()
+
1
]
=
'>'
;
filename_buffer
[
stripped_include
.
size
()
+
2
]
=
'\0'
;
return
filename_buffer
.
data
();
return
{
filename_buffer
.
data
()
,
stripped_include
.
size
()
+
2
}
;
}
}
// namespace megadep
src/megadep/filesystem.test.cpp
View file @
15a8e46f
...
...
@@ -102,51 +102,71 @@ TEST_CASE("megadep::read_includes reads all the #include statements",
TEST_CASE
(
"megadep::resolve_include resolves the #include statements"
,
"[resolve_include][filesystem][unit]"
)
{
auto
includes
=
megadep
::
read_includes
(
this_file
);
// Also include:
// #include "filesystem.hpp"
// #include "megadep/filesystem.hpp"
// #include "cstdio"
includes
.
emplace_back
(
"
\"
filesystem.hpp
\"
"
);
includes
.
emplace_back
(
"
\"
megadep/filesystem.hpp
\"
"
);
includes
.
emplace_back
(
"
\"
cstdio
\"
"
);
const
std
::
string
megadep_include
=
"src"
;
const
std
::
string
catch2_include
=
"external/catch2/single_include"
;
const
std
::
vector
<
std
::
string
>
search_directories
=
{
megadep_include
,
catch2_include
};
for
(
auto
&
include
:
includes
)
{
auto
resolved_include
=
megadep
::
resolve_include
(
include
,
this_file
,
search_directories
);
SECTION
(
"read includes from this_file."
)
{
auto
includes
=
megadep
::
read_includes
(
this_file
);
for
(
auto
&
include
:
includes
)
{
auto
resolved_include
=
megadep
::
resolve_include
(
include
,
this_file
,
search_directories
);
// remove <, >, ", '.
const
std
::
string
stripped_include
=
include
.
substr
(
1
,
include
.
size
()
-
2
);
std
::
string_view
megadep_prefix
=
"megadep/"
;
std
::
string_view
catch2_prefix
=
"catch2/"
;
// expect <megadep/...> and "megadep/..." to resolve to the src/megadep
// directory.
if
(
stripped_include
.
substr
(
0
,
megadep_prefix
.
size
())
==
megadep_prefix
)
{
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
REQUIRE
(
resolved_include
==
megadep_include
+
"/"
+
stripped_include
);
}
// expect <catch2/...> to resolve to the external/catch2/... directory.
else
if
(
stripped_include
.
substr
(
0
,
catch2_prefix
.
size
())
==
catch2_prefix
)
{
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
REQUIRE
(
resolved_include
==
catch2_include
+
"/"
+
stripped_include
);
}
// expect everything else to be unresolved.
else
{
REQUIRE
(
resolved_include
==
std
::
string
{
'<'
}
+
stripped_include
+
">"
);
}
}
}
// remove <, >, ", '.
const
std
::
string
stripped_include
=
include
.
substr
(
1
,
include
.
size
()
-
2
);
SECTION
(
"quoted includes and relative directories"
)
{
const
auto
resolve_include
=
[
&
](
std
::
string_view
include
)
{
return
megadep
::
resolve_include
(
include
,
this_file
,
search_directories
);
};
std
::
string_view
megadep_prefix
=
"megadep/"
;
std
::
string_view
catch2_prefix
=
"catch2/"
;
// #include "filesystem.hpp" -> src/megadep/filesystem.hpp
REQUIRE
(
resolve_include
(
"
\"
filesystem.hpp
\"
"
)
==
"src/megadep/filesystem.hpp"
);
// expect <megadep/...> and "megadep/..." to resolve to the src/megadep
// directory.
if
(
stripped_include
.
substr
(
0
,
megadep_prefix
.
size
())
==
megadep_prefix
)
{
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
REQUIRE
(
resolved_include
==
megadep_include
+
"/"
+
stripped_include
);
}
// expect "fileystem.hpp" to resolve to the src/megadep directory.
else
if
(
stripped_include
==
"filesystem.hpp"
)
{
REQUIRE
(
resolved_include
==
std
::
string
{
this_directory
}
+
"/"
+
stripped_include
);
}
// expect <catch2/...> to resolve to the external/catch2/... directory.
else
if
(
stripped_include
.
substr
(
0
,
catch2_prefix
.
size
())
==
catch2_prefix
)
{
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
REQUIRE
(
resolved_include
==
catch2_include
+
"/"
+
stripped_include
);
}
// expect everything else to be unresolved.
else
{
REQUIRE
(
resolved_include
==
std
::
string
{
'<'
}
+
stripped_include
+
">"
);
}
// #include "./filesystem.hpp" -> src/megadep/filesystem.hpp
REQUIRE
(
resolve_include
(
"
\"
./filesystem.hpp
\"
"
)
==
"src/megadep/filesystem.hpp"
);
// #include "../megadep/filesystem.hpp" -> src/megadep/filesystem.hpp
REQUIRE
(
resolve_include
(
"
\"
../megadep/filesystem.hpp
\"
"
)
==
"src/megadep/filesystem.hpp"
);
// #include "megadep/filesystem.hpp" -> src/megadep/filesystem.hpp
REQUIRE
(
resolve_include
(
"
\"
megadep/filesystem.hpp
\"
"
)
==
"src/megadep/filesystem.hpp"
);
// #include "megadep/../megadep/filesystem.hpp"
// -> src/megadep/filesystem.hpp
REQUIRE
(
resolve_include
(
"
\"
megadep/../megadep/filesystem.hpp
\"
"
)
==
"src/megadep/filesystem.hpp"
);
// #include "cstdio" -> <cstdio>
REQUIRE
(
resolve_include
(
"
\"
cstdio
\"
"
)
==
"<cstdio>"
);
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment