diff --git a/automotive/BUILD.bazel b/automotive/BUILD.bazel index 976f7da7ba00..4a6b7390feb7 100644 --- a/automotive/BUILD.bazel +++ b/automotive/BUILD.bazel @@ -381,6 +381,7 @@ drake_cc_library( ":trajectory_car", "//automotive/maliput/api", "//automotive/maliput/utility", + "//common:temp_directory", "//lcm", "//multibody/parsers", "//multibody/rigid_body_plant:drake_visualizer", diff --git a/automotive/automotive_simulator.cc b/automotive/automotive_simulator.cc index 884d5232bc3d..0b926947c3c0 100644 --- a/automotive/automotive_simulator.cc +++ b/automotive/automotive_simulator.cc @@ -14,6 +14,7 @@ #include "drake/automotive/maliput/utility/generate_urdf.h" #include "drake/automotive/prius_vis.h" #include "drake/common/drake_throw.h" +#include "drake/common/temp_directory.h" #include "drake/common/text_logging.h" #include "drake/lcm/drake_lcm.h" #include "drake/lcmt_viewer_draw.hpp" @@ -443,10 +444,11 @@ void AutomotiveSimulator::GenerateAndLoadRoadNetworkUrdf() { std::string filename = road_->id().string(); std::transform(filename.begin(), filename.end(), filename.begin(), [](char ch) { return ch == ' ' ? '_' : ch; }); + const std::string tmpdir = drake::temp_directory(); maliput::utility::GenerateUrdfFile(road_.get(), - "/tmp", filename, + tmpdir, filename, maliput::utility::ObjFeatures()); - const std::string urdf_filepath = "/tmp/" + filename + ".urdf"; + const std::string urdf_filepath = tmpdir + "/" + filename + ".urdf"; parsers::urdf::AddModelInstanceFromUrdfFileToWorld( urdf_filepath, drake::multibody::joints::kFixed, diff --git a/bindings/pydrake/common_py.cc b/bindings/pydrake/common_py.cc index 71eef48f89e2..35f0517777ac 100644 --- a/bindings/pydrake/common_py.cc +++ b/bindings/pydrake/common_py.cc @@ -7,6 +7,7 @@ #include "drake/common/drake_assertion_error.h" #include "drake/common/drake_path.h" #include "drake/common/find_resource.h" +#include "drake/common/temp_directory.h" namespace drake { namespace pydrake { @@ -50,6 +51,10 @@ PYBIND11_MODULE(_common_py, m) { "e.g., drake/examples/pendulum/Pendulum.urdf. Raises an exception " "if the resource was not found.", py::arg("resource_path")); + m.def("temp_directory", &temp_directory, + "Returns a directory location suitable for temporary files that is " + "the value of the environment variable TEST_TMPDIR if defined or " + "otherwise /tmp. Any trailing / will be stripped from the output."); // Returns the fully-qualified path to the root of the `drake` source tree. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/bindings/pydrake/test/common_test.py b/bindings/pydrake/test/common_test.py index ee764fcd079d..2c3400554931 100644 --- a/bindings/pydrake/test/common_test.py +++ b/bindings/pydrake/test/common_test.py @@ -1,11 +1,12 @@ from __future__ import absolute_import, division, print_function +import os import unittest import pydrake.common class TestCommon(unittest.TestCase): - def testDrakeDemandThrows(self): + def test_drake_demand_throws(self): # Drake's assertion errors should turn into SystemExit by default, # without the user needing to do anything special. Here, we trigger a # C++ assertion failure from Python and confirm that an exception with @@ -23,7 +24,11 @@ def testDrakeDemandThrows(self): " condition 'false' failed", ])) - def testDrakeFindResourceOrThrow(self): + def test_find_resource_or_throw(self): pydrake.common.FindResourceOrThrow( 'drake/examples/atlas/urdf/atlas_convex_hull.urdf' ) + + def test_temp_directory(self): + self.assertEqual(os.environ.get('TEST_TMPDIR'), + pydrake.common.temp_directory()) diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 518755248d05..23ce41dbfa76 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -45,6 +45,7 @@ drake_cc_library( ":sorted_vectors_have_intersection", ":symbolic", ":symbolic_decompose", + ":temp_directory", ":type_safe_index", ":unused", ], @@ -355,6 +356,16 @@ drake_cc_library( ], ) +drake_cc_library( + name = "temp_directory", + srcs = ["temp_directory.cc"], + hdrs = ["temp_directory.h"], + deps = [ + ":essential", + "@spruce", + ], +) + drake_cc_library( name = "text_logging_gflags", hdrs = ["text_logging_gflags.h"], @@ -942,6 +953,13 @@ drake_cc_googletest( ], ) +drake_cc_googletest( + name = "temp_directory_test", + deps = [ + ":temp_directory", + ], +) + drake_cc_googletest( name = "text_logging_test", deps = [ diff --git a/common/proto/BUILD.bazel b/common/proto/BUILD.bazel index 37d1b2cc7f9a..610333f2d561 100644 --- a/common/proto/BUILD.bazel +++ b/common/proto/BUILD.bazel @@ -36,6 +36,7 @@ drake_cc_library( deps = [ ":matlab_rpc", "//common:essential", + "//common:temp_directory", ], ) @@ -55,6 +56,7 @@ drake_cc_library( deps = [ ":call_matlab", "//common:copyable_unique_ptr", + "//common:temp_directory", ], ) diff --git a/common/proto/call_matlab.cc b/common/proto/call_matlab.cc index e8f9cf1d1fb5..7ed91c555f02 100644 --- a/common/proto/call_matlab.cc +++ b/common/proto/call_matlab.cc @@ -11,6 +11,7 @@ #include "drake/common/drake_assert.h" #include "drake/common/never_destroyed.h" +#include "drake/common/temp_directory.h" namespace drake { namespace common { @@ -117,7 +118,8 @@ CreateOutputStream(const std::string& filename) { void PublishCallMatlab(const MatlabRPC& message) { // TODO(russt): Provide option for setting the filename. - static auto raw_output = CreateOutputStream("/tmp/matlab_rpc"); + const std::string output_file = temp_directory() + "/matlab_rpc"; + static auto raw_output = CreateOutputStream(output_file); PublishCall(raw_output.get(), message); } diff --git a/common/proto/call_python.cc b/common/proto/call_python.cc index 205e39596f22..fa2cab406d31 100644 --- a/common/proto/call_python.cc +++ b/common/proto/call_python.cc @@ -3,6 +3,7 @@ #include #include "drake/common/proto/matlab_rpc.pb.h" +#include "drake/common/temp_directory.h" namespace drake { namespace common { @@ -27,14 +28,13 @@ void ToMatlabArray(const PythonRemoteVariable& var, MatlabArray* matlab_array) { namespace { -const char kFilenameDefault[] = "/tmp/python_rpc"; - auto GetPythonOutputStream(const std::string* filename = nullptr) { static std::unique_ptr raw_output; if (!raw_output) { // If we do not yet have a file, create it. + const std::string filename_default = temp_directory() + "/python_rpc"; raw_output = - internal::CreateOutputStream(filename ? *filename : kFilenameDefault); + internal::CreateOutputStream(filename ? *filename : filename_default); } else { // If we already have a file, ensure that this does not come from // `CallPythonInit`. diff --git a/common/proto/call_python_client.py b/common/proto/call_python_client.py index 5fa29baf5145..ffc9ae48110a 100644 --- a/common/proto/call_python_client.py +++ b/common/proto/call_python_client.py @@ -242,9 +242,6 @@ def magic(N): locals()) -_FILENAME_DEFAULT = "/tmp/python_rpc" - - class CallPythonClient(object): """Provides a client to receive Python commands. @@ -255,7 +252,9 @@ def __init__(self, filename=None, stop_on_error=True, scope_globals=None, scope_locals=None, threaded=True, wait=False): if filename is None: - self.filename = _FILENAME_DEFAULT + # TODO(jamiesnape): Use drake.common.temp_directory. + temp_directory = os.environ.get("TEST_TMPDIR", "/tmp") + self.filename = os.path.join(temp_directory, "python_rpc") else: self.filename = filename # Scope. Give it access to everything here. diff --git a/common/proto/test/call_python_full_test.sh b/common/proto/test/call_python_full_test.sh index 7d980e3ca6c2..b4a084794370 100755 --- a/common/proto/test/call_python_full_test.sh +++ b/common/proto/test/call_python_full_test.sh @@ -35,7 +35,7 @@ cc_bin=${cur}/call_python_test py_client_cli=${cur}/call_python_client_cli # TODO(eric.cousineau): Use `tempfile` once we can choose which file C++ # uses. -filename=$(mktemp) +filename="${TEST_TMPDIR}/python_rpc" done_file=${filename}_done cc_bin_flags= diff --git a/common/temp_directory.cc b/common/temp_directory.cc new file mode 100644 index 000000000000..eed6ada9c2fc --- /dev/null +++ b/common/temp_directory.cc @@ -0,0 +1,24 @@ +#include "drake/common/temp_directory.h" + +#include + +#include + +#include "drake/common/drake_throw.h" + +namespace drake { + +std::string temp_directory() { + // TODO(jamiesnape): Use mkdtemp instead of simply returning /tmp for + // applications that do not require a hardcoded /tmp. + const char* path_str = nullptr; + (path_str = std::getenv("TEST_TMPDIR")) || (path_str = "/tmp"); + + // Spruce normalizes the path and strips any trailing /. + spruce::path path(path_str); + DRAKE_THROW_UNLESS(path.isDir()); + + return path.getStr(); +} + +} // namespace drake diff --git a/common/temp_directory.h b/common/temp_directory.h new file mode 100644 index 000000000000..5790f7971203 --- /dev/null +++ b/common/temp_directory.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace drake { + +/// Returns a directory location suitable for temporary files. +/// @return The value of the environment variable TEST_TMPDIR if defined or +/// otherwise /tmp. Any trailing / will be stripped from the output. +/// @throws std::runtime_error If the path referred to by TEST_TMPDIR or /tmp +/// does not exist or is not a directory. +std::string temp_directory(); + +} // namespace drake diff --git a/common/test/temp_directory_test.cc b/common/test/temp_directory_test.cc new file mode 100644 index 000000000000..48ab886954e6 --- /dev/null +++ b/common/test/temp_directory_test.cc @@ -0,0 +1,35 @@ +#include "drake/common/temp_directory.h" + +#include +#include + +#include + +namespace drake { +namespace { + +GTEST_TEST(TempDirectoryTest, TestTmpdirSet) { + const char* test_tmpdir = std::getenv("TEST_TMPDIR"); + ASSERT_STRNE(nullptr, test_tmpdir); + + const std::string temp_directory_with_test_tmpdir_set = temp_directory(); + EXPECT_NE('/', temp_directory_with_test_tmpdir_set.back()); + EXPECT_EQ(std::string(test_tmpdir), temp_directory_with_test_tmpdir_set); +} + +GTEST_TEST(TempDirectoryTest, TestTmpdirUnset) { + const char* test_tmpdir = std::getenv("TEST_TMPDIR"); + ASSERT_STRNE(nullptr, test_tmpdir); + + const int unset_result = ::unsetenv("TEST_TMPDIR"); + ASSERT_EQ(0, unset_result); + + const std::string temp_directory_with_test_tmpdir_unset = temp_directory(); + EXPECT_EQ("/tmp", temp_directory_with_test_tmpdir_unset); + + const int setenv_result = ::setenv("TEST_TMPDIR", test_tmpdir, 1); + ASSERT_EQ(0, setenv_result); +} + +} // namespace +} // namespace drake diff --git a/matlab/call_matlab_client.cc b/matlab/call_matlab_client.cc index 5ef78ea6f162..ddba2e3c66e2 100644 --- a/matlab/call_matlab_client.cc +++ b/matlab/call_matlab_client.cc @@ -18,6 +18,7 @@ #include "drake/common/drake_assert.h" #include "drake/common/proto/matlab_rpc.pb.h" +#include "drake/common/temp_directory.h" #include "drake/common/unused.h" namespace { @@ -101,7 +102,7 @@ void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { drake::unused(nlhs); drake::unused(plhs); - std::string filename = "/tmp/matlab_rpc"; + std::string filename = drake::temp_directory() + "/matlab_rpc"; if (nrhs == 1) { filename = mxGetStdString(prhs[0]); diff --git a/solvers/BUILD.bazel b/solvers/BUILD.bazel index 2d30f11adadf..145fc52c68ea 100644 --- a/solvers/BUILD.bazel +++ b/solvers/BUILD.bazel @@ -1117,6 +1117,7 @@ drake_cc_googletest( ":mathematical_program_test_util", ":optimization_examples", ":quadratic_program_examples", + "//common:temp_directory", "//common/test_utilities:eigen_matrix_compare", "@spruce", ], diff --git a/solvers/test/snopt_solver_test.cc b/solvers/test/snopt_solver_test.cc index 84eb157f7a6c..45260525d878 100644 --- a/solvers/test/snopt_solver_test.cc +++ b/solvers/test/snopt_solver_test.cc @@ -3,6 +3,7 @@ #include #include +#include "drake/common/temp_directory.h" #include "drake/common/test_utilities/eigen_matrix_compare.h" #include "drake/solvers/mathematical_program.h" #include "drake/solvers/test/linear_program_examples.h" @@ -88,7 +89,7 @@ GTEST_TEST(SnoptTest, TestSetOption) { EXPECT_EQ(result, SolutionResult::kIterationLimit); // This is to verify we can set the print out file. - std::string print_file = std::string(::getenv("TEST_TMPDIR")) + "/snopt.out"; + std::string print_file = temp_directory() + "/snopt.out"; std::cout << print_file << std::endl; EXPECT_FALSE(spruce::path(print_file).exists()); prog.SetSolverOption(SnoptSolver::id(), "Print file", print_file); diff --git a/tools/install/libdrake/build_components.bzl b/tools/install/libdrake/build_components.bzl index 974611ccd645..38f00bfdd97f 100644 --- a/tools/install/libdrake/build_components.bzl +++ b/tools/install/libdrake/build_components.bzl @@ -82,6 +82,7 @@ LIBDRAKE_COMPONENTS = [ "//common:sorted_vectors_have_intersection", "//common:symbolic", "//common:symbolic_decompose", + "//common:temp_directory", "//common:text_logging_gflags_h", "//common:type_safe_index", "//common:unused",