Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 210 additions & 1 deletion rdmd.d
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ private string[string] getDependencies(string rootModule, string workDir,
scope(exit) collectException(depsReader.close()); // don't care for errors

// Fetch all dependencies and append them to myDeps
auto pattern = regex(r"^(import|file|binary|config|library)\s+([^\(]+)\(?([^\)]*)\)?\s*$");
auto pattern = ctRegex!r"^(import|file|binary|config|library)\s+([^\(]+)\(?([^\)]*)\)?\s*$";
string[string] result;
foreach (string line; lines(depsReader))
{
Expand Down Expand Up @@ -693,6 +693,17 @@ private string[string] getDependencies(string rootModule, string workDir,

immutable rootDir = dirName(rootModule);

// For most single-file builds we don't have any dependencies except the standard library
immutable file = readText(rootModule);
immutable hasDependencies = needsInspection(file);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be wrapped in a collectException:

bool hasDependencies;
if (auto ex = collectException(needsInspection(file), hasDependencies))
{
    // needsInspection unexpectedly failed, proceed with normal compilation
    hasDependencies = true;

    // but throw if we're debugging RDMD
    debug throw ex;
}

if (!hasDependencies)
{
import std.file : writeFile = write;
// run dmd with an empty file to get it's configuration
rootModule = buildPath(workDir, "emptyFile.d");
rootModule.writeFile("");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return null?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting :)

Is there an opportunity for code reuse with --main?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well then I have to modify the lines below. As we have to write an empty file anyways (DMD expects at least one file) , I thought this was the option with the least changes.

Copy link
Contributor Author

@wilzbach wilzbach Aug 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw the additional cost to write the file & call DMD with an empty file seems to be very small:

> python -m timeit -n 10 -r 3 -s 'import os' 'os.system("rdmd --force foo.d > /dev/null")'
10 loops, best of 3: 744 msec per loop
> python -m timeit -n 10 -r 3 -s 'import os' 'os.system("./rdmd --force foo.d > /dev/null")'
10 loops, best of 3: 455 msec per loop

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the main function because you don't want to link anyway. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the main function because you don't want to link anyway. Right?

Oh nice! I didn't realize dmd would compile an empty file :)


// Collect dependencies
auto depsGetter =
// "cd "~shellQuote(rootDir)~" && "
Expand All @@ -714,9 +725,207 @@ private string[string] getDependencies(string rootModule, string workDir,
exit(depsExitCode);
}

if (!hasDependencies)
std.file.remove(rootModule);

return dryRun ? null : readDepsFile();
}

bool needsInspection(string file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs documentation in its header.

{
import std.utf : byChar;

// an import statement can be at the beginning of a line or preceded by
// at least one whitespace
// a whitespace after `import` is mandatory
foreach (match; file.matchAll(ctRegex!(`(?<=^|\s)import\s+([^;]*);`, "ms")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import can also appear after these: ;{}. There may be more. Same with mixin below.

{
// selective imports can only be at the end of an ImportList
// hence we can just chop of the imports
auto selectiveImportStart = match[1].byChar.countUntil(':');
if (selectiveImportStart < 0)
selectiveImportStart = match[1].length;

// the ImportList of packages can be separated with commas
foreach (importText; match[1][0..selectiveImportStart].splitter(","))
{
string mod;

// for renamed imports the second part is the package name
if (auto renamedImport = importText.matchFirst(ctRegex!(`[^=]*=\s*(.*)`, "s")))
mod = renamedImport[1];
else
mod = importText.strip;

// standard library and runtime are included by default and have no dependencies
if (!exclusions.any!(x => mod.startsWith(x.chain("."))))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the advice about using the exclusions array @aG0aep6G !

Is "chain" cheap enough or should I better allocate an exclusionsWithDot array at the top/start?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, chain is cheap

return true;
}
}

// Mixins are tricky -> do the full inspection to be sure
// A `mixin` statement must be preceded by at least one whitespace or the beginning of a line
if (file.matchFirst(ctRegex!(`(?<=^|\s)mixin[^(a-zA-Z0-9_]*\(`, "ms")))
return true;

return false;
Copy link
Member

@andralex andralex Aug 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return file.matchFirst(ctRegex!(`mixin[^(]*\(`, "s"));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This obfuscates the code, since it's one in a series of checks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

}

// check imports from the standard library or runtime
unittest
{
assert(!"import std.traits;".needsInspection);
assert(!"import core.runtime;".needsInspection);
assert(!"public import std.traits;".needsInspection);

// import as an identifiers
assert(!"Import foo;".needsInspection);
assert(!"importline foo;".needsInspection);
assert(!"myimport foo;".needsInspection);

// multiple imports
assert(!"import std.traits, std.math;".needsInspection);
assert(!"import std.traits; import std.math;".needsInspection);
assert(!"import std.traits ,std.math, core.runtime;".needsInspection);
assert(!"import std.traits;\nimport std.math;".needsInspection);

// selective imports
assert(!"import std.math: exp;".needsInspection);
assert(!"import std.math : cos, exp, sin;".needsInspection);

// selective imports can be the last part of an ImportList
assert(!"import std.traits, std.math : exp;".needsInspection);
assert(!"import std.traits, std.math : cos, exp, sin;".needsInspection);

// renamed imports
assert(!"import io = std.stdio;".needsInspection);
assert(!"import io = std.stdio, math = std.math;".needsInspection);

// selective renamed imports
assert(!"import stdio = std.stdio: writeln, dump = write;".needsInspection);
assert(!"import traits = std.traits: isFloatingPoint;".needsInspection);
assert(!"import math = std.math, stdio = std.stdio: writeln, dump = write;".needsInspection);

// multi-line
assert(!q{
import
std.traits
;
}.needsInspection);

assert(!q{
import
io
=
std.stdio,
math = std.math
;
}.needsInspection);
}

// check imports from third-party libraries
unittest
{
assert("import foo;".needsInspection);
assert("import bar;".needsInspection);
assert("public import foo.bar;".needsInspection);

// check for the last dot
assert("import stdio;".needsInspection);

// multiple imports
assert("import std.traits, foo;".needsInspection);
assert("import std.traits; import foo;".needsInspection);
assert("import foo; import std.traits".needsInspection);
assert("import bar ,std.math, core.runtime;".needsInspection);
assert("import std.traits;\nimport foo;".needsInspection);
assert("import foo;\nimport std.traits;".needsInspection);

// selective imports
assert("import foo: exp;".needsInspection);
assert("import foo.bar : cos, exp, sin;".needsInspection);

// selective imports can be the last part of an ImportList
assert("import std.traits, foo.bar : exp;".needsInspection);
assert("import bar, std.math : exp;".needsInspection);
assert("import std.traits, bar : cos, exp, sin;".needsInspection);

// renamed imports
assert("import io = foo.bar;".needsInspection);
assert("import stdio = foo.bar;".needsInspection);
assert("import io = std.stdio, math = foo.bar;".needsInspection);
assert("import io = foo, math = std.math;".needsInspection);

// selective renamed imports
assert("import stdio = foo.bar: writeln, dump = write;".needsInspection);
assert("import traits = foo.bar: isFloatingPoint;".needsInspection);
assert("import math = std.math, stdio = foo.bar: writeln, dump = write;".needsInspection);
assert("import math = foo.bar, stdio = std.stdio: writeln, dump = write;".needsInspection);

// multi-line
assert(q{
import
foo
;
}.needsInspection);

assert(q{
import
io
=
std.stdio,
math = foo.bar
;
}.needsInspection);
}

// check for mixins
// for now we don't inspect the mixin itself and always trigger an inspection
unittest
{
assert(q{
void main() {
mixin("import foo;");
}
}.needsInspection);

assert(q{
void main() {
mixin("imp" ~ "ort foo;");
}
}.needsInspection);

assert(q{
void main() {
enum foo = `string a = "Foo"; im` ~ `port stdio;`;
mixin (b);
}
}.needsInspection);

// multi-line
assert(q{
void main() {
enum foo = "string a = \"Foo\"; import " ~ " foo;";
mixin
(
b
)
;
}
}.needsInspection);

// import as an identifiers
assert(!"Mixin(foo);".needsInspection);
assert(!"mymixin (foo);".needsInspection);
assert(!"mixinTrick (foo);".needsInspection);

// for templates
assert(!"mixin foo;".needsInspection);

assert("int a = 2;\nmixin(`import foo`);".needsInspection);
assert("mixin(`import foo`);\n int a = 2;".needsInspection);
}

// Is any file newer than the given file?
bool anyNewerThan(T)(T files, in string file)
{
Expand Down