diff --git a/dstep/driver/Application.d b/dstep/driver/Application.d index da1c98f7..e9a8d997 100644 --- a/dstep/driver/Application.d +++ b/dstep/driver/Application.d @@ -115,8 +115,9 @@ class Application import std.algorithm; import std.array; import std.range; + import std.path; - import dstep.driver.Util : makeDefaultOutputFile; + import dstep.driver.Util : makeDefaultOutputFile, findBasePath; auto inputFiles = config.inputFiles; @@ -130,11 +131,19 @@ class Application } else { - alias fmap = file => Path.buildPath( - config.output, - makeDefaultOutputFile(file, false)); - - return inputFiles.map!fmap.array; + if (config.output.empty) + { + alias fmap = file => makeDefaultOutputFile(file, false); + return inputFiles.map!fmap.array; + } + else + { + auto basePath = findBasePath(inputFiles); + alias fmap = file => Path.buildPath( + config.output, + makeDefaultOutputFile(relativePath(file, basePath), false)); + return inputFiles.map!fmap.array; + } } } diff --git a/dstep/driver/Util.d b/dstep/driver/Util.d index 90e4d3d6..eceaa693 100644 --- a/dstep/driver/Util.d +++ b/dstep/driver/Util.d @@ -15,3 +15,89 @@ string makeDefaultOutputFile(string inputFile, bool useBaseName = true) return setExtension(inputFile, "d"); } + +string findBasePath(string[] paths) +{ + import std.algorithm.iteration : filter, map, reduce; + import std.algorithm.searching : commonPrefix; + import std.array : array; + import std.path : isAbsolute, dirName, pathSplitter, buildPath; + + auto absolutePaths = paths.filter!isAbsolute.array; + if (absolutePaths.length == 0) + return "."; + + version (Windows) + { + import std.string : replace; + absolutePaths = absolutePaths.map!(p => p.replace("/", "\\")).array; + } + + if (absolutePaths.length == 1) + return dirName(absolutePaths[0]); + + auto parts = absolutePaths + .map!(p => pathSplitter(dirName(p)).array) + .array; + + version (Windows) + { + import std.uni : icmp; + auto common = parts.reduce!((a, b) => commonPrefix!((x, y) => icmp(x, y) == 0)(a, b).array); + } + else + { + auto common = parts.reduce!((a, b) => commonPrefix(a, b).array); + } + + if (common.length == 0) + return "."; + + return buildPath(common); +} + +unittest +{ + assert(findBasePath([]) == "."); + assert(findBasePath(["foo/bar.h", "a/b.h", "c.h"]) == "."); +} + +version (Posix) +{ + unittest + { + assert(findBasePath(["/usr/include/stdio.h"]) == "/usr/include"); + assert(findBasePath(["foo.h", "/usr/include/sys/stat.h"]) == "/usr/include/sys"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/lib/foo.h"]) == "/usr"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/string.h", "/usr/include/stdlib.h"]) == "/usr/include"); + assert(findBasePath(["usr.h", "/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/include/string.h", "include.h"]) == "/usr/include"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/Include/stdio.h", "/usR/include/stdio.h"]) == "/"); + } +} + +version (Windows) +{ + unittest + { + assert(findBasePath(["/usr/include/stdio.h"]) == "."); + assert(findBasePath(["foo.h", "/usr/include/sys/stat.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/lib/foo.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/string.h", "/usr/include/stdlib.h"]) == "."); + assert(findBasePath(["usr.h", "/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/include/string.h", "include.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/Include/stdio.h", "/usR/include/stdio.h"]) == "."); + + assert(findBasePath(["C:/usr/include/stdio.h"]) == "C:\\usr\\include"); + assert(findBasePath(["foo.h", "C:/usr/include/sys/stat.h"]) == "C:\\usr\\include\\sys"); + assert(findBasePath(["C:/usr/include/stdio.h", "C:/usr/include/sys/stat.h", "C:/usr/lib/foo.h"]) == "C:\\usr"); + assert(findBasePath(["C:/usr/include/stdio.h", "C:/usr/include/string.h", "C:/usr/include/stdlib.h"]) == "C:\\usr\\include"); + assert(findBasePath(["usr.h", "D:/usr/include/stdio.h", "D:/usr/include/sys/stat.h", "D:/usr/include/string.h", "include.h"]) == "D:\\usr\\include"); + assert(findBasePath(["E:/usr/include/stdio.h", "E:/usr/Include/stdio.h", "E:/usR/include/stdio.h"]) == "E:\\usr\\include"); + + assert(findBasePath(["D:\\test.h"]) == "D:\\"); + assert(findBasePath(["D:\\a\\b.h", "D:\\a\\c.h"]) == "D:\\a"); + assert(findBasePath(["D:\\a\\b.h", "E:\\b.h"]) == "."); + assert(findBasePath(["D:\\B\\a.h", "D:\\b\\c\\D.h", "D:\\B\\c.h"]) == "D:\\B"); + assert(findBasePath(["D:/a\\b/d.h", "D:\\a/B\\c.h"]) == "D:\\a\\b"); + assert(findBasePath(["C:\\a.h", "D:\\b.h"]) == "."); + } +} diff --git a/tests/functional/Tests.d b/tests/functional/Tests.d index 7eebf707..9c255403 100644 --- a/tests/functional/Tests.d +++ b/tests/functional/Tests.d @@ -9,6 +9,7 @@ import tests.support.Assertions; import std.algorithm : canFind; import std.process : executeShell; import std.typecons; +import std.path : absolutePath; void printClangVersion() { @@ -294,6 +295,44 @@ unittest assertIssuesWarning("tests/functional/collision.h"); } +// DStep should output to correct directory when using absolute paths. +unittest +{ + assertRunsDStepCFiles([ + TestFile("tests/functional/graph/subfile1.d", absolutePath("tests/functional/graph/subfile1.h")), + TestFile("tests/functional/include/subfile3.d", absolutePath("tests/functional/include/subfile3.h"))] + ); +} + +unittest +{ + assertRunsDStepCFile( + "tests/functional/functions.d", + absolutePath("tests/functional/functions.h") + ); +} + +// Output in same directory when single file given and no output specified +unittest +{ + assertRunsDStepCFiles( + [TestFile("tests/functional/functions.d", absolutePath("tests/functional/functions.h"))], + ["--unspecified-output"], + false); +} + +// Output in same directory when using multiple files with no output folder specified +unittest +{ + assertRunsDStepCFiles( + [TestFile("tests/functional/aggregate.d", "tests/functional/aggregate.h"), + TestFile("tests/functional/functions.d", "tests/functional/functions.h"), + TestFile("tests/functional/graph/subfile1.d", absolutePath("tests/functional/graph/subfile1.h")), + TestFile("tests/functional/include/subfile3.d", absolutePath("tests/functional/include/subfile3.h"))], + ["--unspecified-output"] + ); +} + version (OSX): // Objective-C tests diff --git a/tests/functional/graph/subfile1.d b/tests/functional/graph/subfile1.d new file mode 100644 index 00000000..673ce4f1 --- /dev/null +++ b/tests/functional/graph/subfile1.d @@ -0,0 +1,3 @@ +extern (C): + +extern __gshared int content; diff --git a/tests/functional/include/subfile3.d b/tests/functional/include/subfile3.d new file mode 100644 index 00000000..ad13ba87 --- /dev/null +++ b/tests/functional/include/subfile3.d @@ -0,0 +1,3 @@ +extern (C): + +void core (int); diff --git a/tests/support/DStepRunner.d b/tests/support/DStepRunner.d index 2974a408..f73d2037 100644 --- a/tests/support/DStepRunner.d +++ b/tests/support/DStepRunner.d @@ -21,13 +21,13 @@ auto testRunDStep( { import core.exception : AssertError; - import std.algorithm : canFind, map; - import std.file : exists, isFile, readText, rmdirRecurse; - import std.path : buildPath; + import std.algorithm : canFind, map, remove; + import std.file : exists, isFile, readText, mkdirRecurse, rmdirRecurse, copy, getcwd; + import std.path : buildPath, isAbsolute, relativePath, dirName; import std.range : join; - import std.array : empty; + import std.array : empty, array; - import dstep.driver.Util : makeDefaultOutputFile; + import dstep.driver.Util : makeDefaultOutputFile, findBasePath; version (OptionalGNUStep) { @@ -48,31 +48,53 @@ auto testRunDStep( string outputDir = namedTempDir("dstepUnitTest"); scope(exit) rmdirRecurse(outputDir); + auto argumentsLength = arguments.length; + arguments = arguments.remove!(x => x == "--unspecified-output"); + bool unspecifiedOutput = arguments.length < argumentsLength; + string[] outputPaths; + string workDir = getcwd(); - if (sourcePaths.length == 1) - { - outputPaths ~= buildPath(outputDir, - makeDefaultOutputFile(sourcePaths[0], false)); - } - else + auto sourceBasePath = findBasePath(sourcePaths); + foreach (ref sourcePath; sourcePaths) { - foreach (sourcePath; sourcePaths) - outputPaths ~= buildPath(outputDir, - makeDefaultOutputFile(sourcePath, false)); + string path = sourcePath; + if (isAbsolute(sourcePath)) + { + path = relativePath(sourcePath, unspecifiedOutput ? workDir : sourceBasePath); + } + if (unspecifiedOutput) + { + auto updatedSourcePath = buildPath(outputDir, path); + mkdirRecurse(dirName(updatedSourcePath)); + copy(sourcePath, updatedSourcePath); + if (isAbsolute(sourcePath)) + { + sourcePath = updatedSourcePath; + } + } + outputPaths ~= buildPath(outputDir, makeDefaultOutputFile(path, false)); } - auto localCommand = ["./bin/dstep"] ~ sourcePaths ~ arguments; + auto dstepPath = buildPath(workDir, "bin", "dstep"); + auto localCommand = [dstepPath] ~ sourcePaths ~ arguments; - if (outputPaths.length == 1) - localCommand ~= ["-o", outputPaths[0]]; + if (unspecifiedOutput) + { + workDir = outputDir; + } else - localCommand ~= ["-o", outputDir]; + { + if (outputPaths.length == 1) + localCommand ~= ["-o", outputPaths[0]]; + else + localCommand ~= ["-o", outputDir]; + } if (command) *command = join(localCommand, " "); - auto result = execute(localCommand); + auto result = execute(localCommand, workDir: workDir); if (outputContents) outputContents.length = outputPaths.length;