aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/cc/link31
-rw-r--r--build2/cc/link.cxx269
-rw-r--r--build2/cc/windows-rpath.cxx187
-rw-r--r--build2/target4
-rw-r--r--build2/utility2
5 files changed, 314 insertions, 179 deletions
diff --git a/build2/cc/link b/build2/cc/link
index 2652285..05f7c5a 100644
--- a/build2/cc/link
+++ b/build2/cc/link
@@ -5,6 +5,8 @@
#ifndef BUILD2_CC_LINK
#define BUILD2_CC_LINK
+#include <set>
+
#include <build2/types>
#include <build2/utility>
@@ -50,7 +52,8 @@ namespace build2
const function<void (file&,
const string&,
bool,
- bool)>&) const;
+ bool)>&,
+ bool = false) const;
void
append_libraries (strings&, file&, bool, scope&, lorder) const;
@@ -58,8 +61,32 @@ namespace build2
hash_libraries (sha256&, file&, bool, scope&, lorder) const;
void
- rpath_libraries (strings&, file&, bool, scope&, lorder, bool) const;
+ rpath_libraries (strings&, target&, scope&, lorder, bool) const;
+
+ // Windows rpath emulation (windows-rpath.cxx).
+ //
+ struct windows_dll
+ {
+ const string& dll;
+ const string* pdb; // NULL if none.
+ string pdb_storage;
+
+ bool operator< (const windows_dll& y) const {return dll < y.dll;}
+ };
+ using windows_dlls = std::set<windows_dll>;
+
+ timestamp
+ windows_rpath_timestamp (file&, scope&, lorder) const;
+
+ windows_dlls
+ windows_rpath_dlls (file&, scope&, lorder) const;
+
+ void
+ windows_rpath_assembly (file&, scope&, lorder,
+ const string&,
+ timestamp,
+ bool) const;
file&
resolve_library (name,
scope&,
diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx
index d8ccc0e..0c3534c 100644
--- a/build2/cc/link.cxx
+++ b/build2/cc/link.cxx
@@ -1007,6 +1007,10 @@ namespace build2
// *.export.libs is up-to-date (which will happen automatically if it is
// listed as a prerequisite of this library).
//
+ // Storing a reference to library path in proc_lib is legal (it comes
+ // either from the target's path or from one of the *.libs variables
+ // neither of which should change on this run).
+ //
void link::
process_libraries (
scope& top_bs,
@@ -1020,17 +1024,51 @@ namespace build2
const string& path, // Library path.
bool sys)>& proc_lib, // True if system library.
const function<void (file&,
- const string& type, // cc.type
- bool com, // cc. or x.
- bool exp)>& proc_opt) const // *.export.
+ const string& type, // cc.type
+ bool com, // cc. or x.
+ bool exp)>& proc_opt, // *.export.
+ bool self /*= false*/) const // Call proc_lib on l?
{
- bool impl (proc_impl && proc_impl (l, la));
+ // Determine if an absolute path is to a system library. Note that
+ // we assume both paths to be normalized.
+ //
+ auto sys = [] (const dir_paths& sysd, const string& p) -> bool
+ {
+ size_t pn (p.size ());
+
+ for (const dir_path& d: sysd)
+ {
+ const string& ds (d.string ()); // Can be "/", otherwise no slash.
+ size_t dn (ds.size ());
+
+ if (pn > dn &&
+ p.compare (0, dn, ds) == 0 &&
+ (path::traits::is_separator (ds[dn - 1]) ||
+ path::traits::is_separator (p[dn])))
+ return true;
+ }
+
+ return false;
+ };
// See what type of library this is (C, C++, etc). Use it do decide
// which x.libs variable name to use. If it's unknown, then we only
// look into prerequisites.
//
const string* t (cast_null<string> (l.vars[c_type]));
+
+ if (self && proc_lib)
+ {
+ const string& p (l.path ().string ());
+
+ bool s (t != nullptr // If cc library (matched or imported).
+ ? cast_false<bool> (l.vars[c_system])
+ : sys (top_sysd, p));
+
+ proc_lib (&l, p, s);
+ }
+
+ bool impl (proc_impl && proc_impl (l, la));
bool cc, same;
auto& vp (var_pool);
@@ -1066,47 +1104,28 @@ namespace build2
}
}
+ scope& bs (t == nullptr || cc ? top_bs : l.base_scope ());
+ optional<lorder> lo; // Calculate lazily.
+ const dir_paths* sysd (nullptr); // Resolve lazily.
+
// Find system search directories corresponding to this library, i.e.,
// from its project and for its type (C, C++, etc).
//
- auto find_sysd = [&top_sysd, this] (
- bool cc, bool same, const string& t, scope& bs) -> const dir_paths&
+ auto find_sysd = [&top_sysd, t, cc, same, &bs, &sysd, this] ()
{
// Use the search dirs corresponding to this library scope/type.
//
- return cc
- ? top_sysd // Imported library, use importer's sysd.
- : cast<dir_paths> (
+ sysd = (t == nullptr || cc)
+ ? &top_sysd // Imported library, use importer's sysd.
+ : &cast<dir_paths> (
bs.root_scope ()->vars[same
? x_sys_lib_dirs
- : var_pool[t + ".sys_lib_dirs"]]);
+ : var_pool[*t + ".sys_lib_dirs"]]);
};
- auto find_lo = [top_lo, this] (file& l, bool cc, scope& bs) -> lorder
+ auto find_lo = [top_lo, t, cc, &bs, &l, &lo, this] ()
{
- return cc ? top_lo : link_order (bs, link_type (l));
- };
-
- // Determine if an absolute path is to a system library. Note that
- // we assume both paths to be normalized.
- //
- auto sys = [] (const dir_paths& sysd, const string& p) -> bool
- {
- size_t pn (p.size ());
-
- for (const dir_path& d: sysd)
- {
- const string& ds (d.string ()); // Can be "/", otherwise no slash.
- size_t dn (ds.size ());
-
- if (pn > dn &&
- p.compare (0, dn, ds) == 0 &&
- (path::traits::is_separator (ds[dn - 1]) ||
- path::traits::is_separator (p[dn])))
- return true;
- }
-
- return false;
+ lo = (t == nullptr || cc) ? top_lo : link_order (bs, link_type (l));
};
// Only go into prerequisites (implementation) if instructed and we are
@@ -1123,23 +1142,12 @@ namespace build2
if ((a = (f = p->is_a<liba> ()) != nullptr)
|| (f = p->is_a<libs> ()) != nullptr)
{
- if (proc_lib)
- {
- const string& p (f->path ().string ());
- proc_lib (f, p, sys (top_sysd, p));
- }
-
- const string& t (cast<string> (f->vars[c_type])); // Must be there.
- bool cc (t == "cc");
- bool same (!cc && t == x);
-
- scope& bs (cc ? top_bs : f->base_scope ());
+ if (sysd == nullptr) find_sysd ();
+ if (!lo) find_lo ();
- process_libraries (bs,
- find_lo (*f, cc, bs),
- find_sysd (cc, same, t, bs),
+ process_libraries (bs, *lo, *sysd,
*f, a,
- proc_impl, proc_lib, proc_opt);
+ proc_impl, proc_lib, proc_opt, true);
}
}
}
@@ -1153,22 +1161,17 @@ namespace build2
if (t == nullptr)
return;
- scope& bs (cc ? top_bs : l.base_scope ());
- optional<lorder> lo; // Calculate lazily.
- const dir_paths* sysd (nullptr); // Resolve lazily.
optional<dir_paths> usrd; // Extract lazily.
// Determine if a "simple path" is a system library.
//
- auto sys_simple = [cc, same, t, &bs, &sysd, &sys, &find_sysd] (
- const string& p) -> bool
+ auto sys_simple = [&sysd, &sys, &find_sysd] (const string& p) -> bool
{
bool s (!path::traits::absolute (p));
if (!s)
{
- if (sysd == nullptr)
- sysd = &find_sysd (cc, same, *t, bs);
+ if (sysd == nullptr) find_sysd ();
s = sys (*sysd, p);
}
@@ -1176,7 +1179,7 @@ namespace build2
return s;
};
- auto proc_int = [&l, cc, same, t,
+ auto proc_int = [&l,
&proc_impl, &proc_lib, &proc_opt,
&sysd, &usrd,
&find_sysd, &find_lo, &sys, &sys_simple,
@@ -1202,11 +1205,8 @@ namespace build2
{
// This is a potentially project-qualified target.
//
- if (sysd == nullptr)
- sysd = &find_sysd (cc, same, *t, bs);
-
- if (!lo)
- lo = find_lo (l, cc, bs);
+ if (sysd == nullptr) find_sysd ();
+ if (!lo) find_lo ();
file& t (resolve_library (n, bs, *lo, *sysd, usrd));
@@ -1217,25 +1217,21 @@ namespace build2
// library's prerequisites (i.e., it is not an implementation
// dependency).
//
- if (t.path ().empty ())
- fail << "target " << t << " is out of date" <<
+ // Note that we used to just check for path being assigned but
+ // on Windows import-installed DLLs may legally have empty
+ // paths.
+ //
+ if (t.mtime (false) == timestamp_unknown)
+ fail << "interface dependency " << t << " is out of date" <<
info << "mentioned in *.export.libs of target " << l <<
info << "is it a prerequisite of " << l << "?";
-
- const string& p (t.path ().string ());
-
- bool s (t.vars[c_type] // If cc library (matched or imported).
- ? cast_false<bool> (t.vars[c_system])
- : sys (*sysd, p));
-
- proc_lib (&t, p, s);
}
// Process it recursively.
//
process_libraries (bs, *lo, *sysd,
t, t.is_a<liba> (),
- proc_impl, proc_lib, proc_opt);
+ proc_impl, proc_lib, proc_opt, true);
}
}
};
@@ -1376,11 +1372,25 @@ namespace build2
file& l, bool la,
scope& bs, lorder lo) const
{
+ bool win (tclass == "windows");
+
auto imp = [] (file&, bool la) {return la;};
- auto lib = [&args] (file* f, const string& p, bool)
+ auto lib = [&args, win] (file* f, const string& p, bool)
{
- args.push_back (f != nullptr ? relative (f->path ()).string () : p);
+ if (f != nullptr)
+ {
+ // On Windows a shared library is a DLL with the import library as a
+ // first ad hoc group member. MinGW though can link directly to DLLs
+ // (see search_library() for details).
+ //
+ if (win && f->member != nullptr && f->is_a<libs> ())
+ f = static_cast<file*> (f->member);
+
+ args.push_back (relative (f->path ()).string ());
+ }
+ else
+ args.push_back (p);
};
auto opt = [&args, this] (file& l, const string& t, bool com, bool exp)
@@ -1400,17 +1410,31 @@ namespace build2
}
};
- process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt);
+ process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt, true);
}
void link::
hash_libraries (sha256& cs, file& l, bool la, scope& bs, lorder lo) const
{
+ bool win (tclass == "windows");
+
auto imp = [] (file&, bool la) {return la;};
- auto lib = [&cs] (file*, const string& p, bool)
+ auto lib = [&cs, win] (file* f, const string& p, bool)
{
- cs.append (p);
+ if (f != nullptr)
+ {
+ // On Windows a shared library is a DLL with the import library as a
+ // first ad hoc group member. MinGW though can link directly to DLLs
+ // (see search_library() for details).
+ //
+ if (win && f->member != nullptr && f->is_a<libs> ())
+ f = static_cast<file*> (f->member);
+
+ cs.append (f->path ().string ());
+ }
+ else
+ cs.append (p);
};
auto opt = [&cs, this] (file& l, const string& t, bool com, bool exp)
@@ -1428,13 +1452,12 @@ namespace build2
}
};
- process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt);
+ process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt, true);
}
void link::
rpath_libraries (strings& args,
- file& l, bool la,
- scope& bs, lorder lo,
+ target& t, scope& bs, lorder lo,
bool for_install) const
{
// Use -rpath-link on targets that support it (Linux, FreeBSD). Note
@@ -1445,14 +1468,6 @@ namespace build2
if (tclass != "linux" && tclass != "freebsd")
return;
}
- else if (!la)
- {
- // Top-level sharen library dependency. It is either matched or
- // imported so should be a cc library.
- //
- if (!cast_false<bool> (l.vars[c_system]))
- args.push_back ("-Wl,-rpath," + l.path ().directory ().string ());
- }
auto imp = [for_install] (file&, bool la)
{
@@ -1532,16 +1547,30 @@ namespace build2
d.args.push_back (move (o));
};
- process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, nullptr);
- }
+ for (target* pt: t.prerequisite_targets)
+ {
+ file* f;
+ liba* a;
- // See windows-rpath.cxx.
- //
- timestamp
- windows_rpath_timestamp (file&);
+ if ((f = a = pt->is_a<liba> ()) ||
+ (f = pt->is_a<libs> ()))
+ {
+ if (!for_install && a == nullptr)
+ {
+ // Top-level sharen library dependency. It is either matched or
+ // imported so should be a cc library.
+ //
+ if (!cast_false<bool> (f->vars[c_system]))
+ args.push_back (
+ "-Wl,-rpath," + f->path ().directory ().string ());
+ }
- void
- windows_rpath_assembly (file&, const string& cpu, timestamp, bool scratch);
+ process_libraries (bs, lo, sys_lib_dirs,
+ *f, a != nullptr,
+ imp, lib, nullptr);
+ }
+ }
+ }
// Filter link.exe noise (msvc.cxx).
//
@@ -1585,7 +1614,7 @@ namespace build2
// it if we are updating for install.
//
if (!for_install)
- rpath_timestamp = windows_rpath_timestamp (t);
+ rpath_timestamp = windows_rpath_timestamp (t, bs, lo);
path mf (
windows_manifest (
@@ -1828,15 +1857,7 @@ namespace build2
// rpath of the imported libraries (i.e., we assume they are also
// installed). But we add -rpath-link for some platforms.
//
- for (target* pt: t.prerequisite_targets)
- {
- file* f;
- liba* a;
-
- if ((f = a = pt->is_a<liba> ()) ||
- (f = pt->is_a<libs> ()))
- rpath_libraries (sargs, *f, a != nullptr, bs, lo, for_install);
- }
+ rpath_libraries (sargs, t, bs, lo, for_install);
if (auto l = t["bin.rpath"])
for (const dir_path& p: cast<dir_paths> (l))
@@ -1882,23 +1903,13 @@ namespace build2
((f = a = pt->is_a<liba> ()) ||
(f = s = pt->is_a<libs> ()))))
{
- // On Windows a shared library is a DLL with the import library as
- // a first ad hoc group member. MinGW though can link directly to
- // DLLs (see search_library() for details).
- //
- if (s != nullptr && tclass == "windows")
- {
- if (s->member != nullptr)
- f = static_cast<file*> (s->member);
- }
-
- cs.append (f->path ().string ());
-
- // Link all the dependent interface libraries (shared) or
- // interface and implementation (static), recursively.
+ // Link all the dependent interface libraries (shared) or interface
+ // and implementation (static), recursively.
//
if (a != nullptr || s != nullptr)
hash_libraries (cs, *f, a != nullptr, bs, lo);
+ else
+ cs.append (f->path ().string ());
}
}
@@ -2118,23 +2129,13 @@ namespace build2
((f = a = pt->is_a<liba> ()) ||
(f = s = pt->is_a<libs> ()))))
{
- // On Windows a shared library is a DLL with the import library as a
- // first ad hoc group member. MinGW though can link directly to DLLs
- // (see search_library() for details).
- //
- if (s != nullptr && tclass == "windows")
- {
- if (s->member != nullptr)
- f = static_cast<file*> (s->member);
- }
-
- sargs.push_back (relative (f->path ()).string ()); // string()&&
-
// Link all the dependent interface libraries (shared) or interface
// and implementation (static), recursively.
//
if (a != nullptr || s != nullptr)
append_libraries (sargs, *f, a != nullptr, bs, lo);
+ else
+ sargs.push_back (relative (f->path ()).string ()); // string()&&
}
}
@@ -2256,7 +2257,7 @@ namespace build2
if (lt == otype::e && tclass == "windows")
{
if (!for_install)
- windows_rpath_assembly (t,
+ windows_rpath_assembly (t, bs, lo,
cast<string> (rs[x_target_cpu]),
rpath_timestamp,
scratch);
diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx
index ea20a5c..e27dddb 100644
--- a/build2/cc/windows-rpath.cxx
+++ b/build2/cc/windows-rpath.cxx
@@ -4,8 +4,6 @@
#include <errno.h> // E*
-#include <set>
-
#include <build2/scope>
#include <build2/context>
#include <build2/variable>
@@ -14,6 +12,8 @@
#include <build2/bin/target>
+#include <build2/cc/link>
+
using namespace std;
using namespace butl;
@@ -36,60 +36,159 @@ namespace build2
// the loader looks for them in the same directory as the DLL). It's not
// clear how well such nested assemblies are supported (e.g., in Wine).
//
+ // What if the DLL is in the same directory as the executable, will it
+ // still be found even if there is an assembly? On the other hand,
+ // handling it as any other won't hurt us much.
+ //
using namespace bin;
// Return the greatest (newest) timestamp of all the DLLs that we will be
// adding to the assembly or timestamp_nonexistent if there aren't any.
//
- timestamp
- windows_rpath_timestamp (file& t)
+ timestamp link::
+ windows_rpath_timestamp (file& t, scope& bs, lorder lo) const
{
timestamp r (timestamp_nonexistent);
- for (target* pt: t.prerequisite_targets)
+ // We need to collect all the DLLs, so go into implementation of both
+ // shared and static (in case they depend on shared).
+ //
+ auto imp = [] (file&, bool) {return true;};
+
+ auto lib = [&r] (file* l, const string& f, bool sys)
{
- if (libs* ls = pt->is_a<libs> ())
+ // We don't rpath system libraries.
+ //
+ if (sys)
+ return;
+
+ // Skip static libraries.
+ //
+ if (l != nullptr)
{
- // Skip installed DLLs.
+ // This can be an "undiscovered" DLL (see search_library()).
//
- if (ls->path ().empty ())
- continue;
-
- // What if the DLL is in the same directory as the executable, will
- // it still be found even if there is an assembly? On the other
- // hand, handling it as any other won't hurt us much.
+ if (!l->is_a<libs> () || l->path ().empty ())
+ return;
+ }
+ else
+ {
+ // This is an absolute path and we need to decide whether it is
+ // a shared or static library.
//
- timestamp t;
-
- if ((t = ls->mtime ()) > r)
- r = t;
+ // @@ This is so broken: we don't link to DLLs, we link to .lib or
+ // .dll.a! Should we even bother? Maybe only for "our" DLLs
+ // (.dll.lib/.dll.a)? But the DLL can also be in a different
+ // directory (lib/../bin).
+ //
+ // Though this can happen on MinGW with direct DLL link...
+ //
+ size_t p (path::traits::find_extension (f));
- if ((t = windows_rpath_timestamp (*ls)) > r)
- r = t;
+ if (p == string::npos || casecmp (f.c_str () + p + 1, "dll") != 0)
+ return;
}
+
+ // Ok, this is a DLL.
+ //
+ timestamp t (l != nullptr ? l->mtime () : file_mtime (f.c_str ()));
+
+ if (t > r)
+ r = t;
+ };
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ file* f;
+ liba* a;
+
+ if ((f = a = pt->is_a<liba> ()) ||
+ (f = pt->is_a<libs> ()))
+ process_libraries (bs, lo, sys_lib_dirs,
+ *f, a != nullptr,
+ imp, lib, nullptr, true);
}
return r;
}
- // Like *_timestamp() but actually collect the DLLs.
+ // Like *_timestamp() but actually collect the DLLs (and weed out the
+ // duplicates).
//
- static void
- rpath_dlls (set<libs*>& s, file& t)
+ auto link::
+ windows_rpath_dlls (file& t, scope& bs, lorder lo) const -> windows_dlls
{
- for (target* pt: t.prerequisite_targets)
+ windows_dlls r;
+
+ auto imp = [] (file&, bool) {return true;};
+
+ auto lib = [&r] (file* l, const string& f, bool sys)
{
- if (libs* ls = pt->is_a<libs> ())
+ if (sys)
+ return;
+
+ if (l != nullptr)
{
- // Skip installed DLLs.
- //
- if (ls->path ().empty ())
- continue;
+ if (l->is_a<libs> () && !l->path ().empty ())
+ {
+ // Get .pdb if there is one (second member of the ad hoc group).
+ //
+ const string* pdb (
+ l->member != nullptr && l->member->member != nullptr
+ ? &static_cast<file&> (*l->member->member).path ().string ()
+ : nullptr);
+
+ r.insert (windows_dll {f, pdb, string ()});
+ }
+ }
+ else
+ {
+ size_t p (path::traits::find_extension (f));
+
+ if (p != string::npos && casecmp (f.c_str () + p + 1, "dll") == 0)
+ {
+ // See if we can find a corresponding .pdb.
+ //
+ windows_dll wd {f, nullptr, string ()};
+ string& pdb (wd.pdb_storage);
+
+ // First try "our" naming: foo.dll.pdb.
+ //
+ pdb = f;
+ pdb += ".pdb";
- s.insert (ls);
- rpath_dlls (s, *ls);
+ if (!file_exists (pdb.c_str ()))
+ {
+ // Then try the usual naming: foo.pdb.
+ //
+ pdb.assign (f, 0, p);
+ pdb += ".pdb";
+
+ if (!file_exists (pdb.c_str ()))
+ pdb.clear ();
+ }
+
+ if (!pdb.empty ())
+ wd.pdb = &pdb;
+
+ r.insert (move (wd));
+ }
}
+ };
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ file* f;
+ liba* a;
+
+ if ((f = a = pt->is_a<liba> ()) ||
+ (f = pt->is_a<libs> ()))
+ process_libraries (bs, lo, sys_lib_dirs,
+ *f, a != nullptr,
+ imp, lib, nullptr, true);
}
+
+ return r;
}
const char*
@@ -103,11 +202,13 @@ namespace build2
// unnecessary work by comparing the DLLs timestamp against the assembly
// manifest file.
//
- void
+ void link::
windows_rpath_assembly (file& t,
+ scope& bs,
+ lorder lo,
const string& tcpu,
timestamp ts,
- bool scratch)
+ bool scratch) const
{
// Assembly paths and name.
//
@@ -139,9 +240,9 @@ namespace build2
//
bool empty (ts == timestamp_nonexistent);
- set<libs*> dlls;
+ windows_dlls dlls;
if (!empty)
- rpath_dlls (dlls, t);
+ dlls = windows_rpath_dlls (t, bs, lo);
// Clean the assembly directory and make sure it exists. Maybe it would
// have been faster to overwrite the existing manifest rather than
@@ -243,18 +344,22 @@ namespace build2
};
- for (libs* dll: dlls)
+ for (const windows_dll& wd: dlls)
{
- const path& dp (dll->path ()); // DLL path.
- const path dn (dp.leaf ()); // DLL name.
+ //@@ Would be nice to avoid copying. Perhaps reuse buffers
+ // by adding path::assign() and traits::leaf().
+ //
+ path dp (wd.dll); // DLL path.
+ path dn (dp.leaf ()); // DLL name.
+
link (dp, ad / dn);
- // Link .pdb if there is one (second member of the ad hoc group).
+ // Link .pdb if there is one.
//
- if (dll->member != nullptr && dll->member->member != nullptr)
+ if (wd.pdb != nullptr)
{
- file& pdb (static_cast<file&> (*dll->member->member));
- link (pdb.path (), ad / pdb.path ().leaf ());
+ path pp (*wd.pdb);
+ link (pp, ad / pp.leaf ());
}
ofs << " <file name='" << dn.string () << "'/>\n";
diff --git a/build2/target b/build2/target
index 103b719..681fcee 100644
--- a/build2/target
+++ b/build2/target
@@ -985,13 +985,13 @@ namespace build2
// should be mtime_target and the members get the mtime from it.
//
timestamp
- mtime () const
+ mtime (bool load = true) const
{
const mtime_target* t (raw_state == target_state::group
? static_cast<const mtime_target*> (group)
: this);
- if (t->mtime_ == timestamp_unknown)
+ if (load && t->mtime_ == timestamp_unknown)
t->mtime_ = t->load_mtime ();
return t->mtime_;
diff --git a/build2/utility b/build2/utility
index 7f05dd0..6c73e8e 100644
--- a/build2/utility
+++ b/build2/utility
@@ -37,6 +37,8 @@ namespace build2
using butl::combine_hash;
using butl::reverse_iterate;
using butl::casecmp;
+ using butl::case_compare_string;
+ using butl::case_compare_c_string;
using butl::lcase;
using butl::alpha;
using butl::alnum;