On 19.07.2011 21:58, Trass3r wrote:
Whats the best way to debug this? Sprinkle printf's around?

Probably. Should happen in that getDependencies function, shouldn't it?

It fails on the return statement in getDependencies.
It never gets to the line after getDependencies in main.

Someone want to look at the assembly? The .exe is to large for the newsgroup, so only the code is attached. I've only tried compiling with dmd 2.054
// Written in the D programming language.

import std.algorithm, std.array, std.c.stdlib, std.datetime,
    std.exception, std.file, std.getopt,
    std.md5, std.path, std.process, std.regexp,
    std.stdio, std.string, std.typetuple;

version (Posix)
{
    enum objExt = ".o";
    enum binExt = "";
}
else version (Windows)
{
    import std.c.windows.windows;
    extern(Windows) HINSTANCE ShellExecuteA(HWND, LPCSTR, LPCSTR, LPCSTR, 
LPCSTR, INT);
    enum objExt = ".obj";
    enum binExt = ".exe";
}
else
{
    static assert(0, "Unsupported operating system.");
}

private bool chatty, buildOnly, dryRun, force;
private string exe, compiler = "dmd";

int main(string[] args)
{
    //writeln("Invoked with: ", map!(q{a ~ ", "})(args));
    if (args.length > 1 && std.algorithm.startsWith(args[1],
                    "--shebang ", "--shebang="))
    {
        // multiple options wrapped in one
        auto a = args[1]["--shebang ".length .. $];
        args = args[0 .. 1] ~ std.string.split(a) ~ args[2 .. $];
    }

    // Continue parsing the command line; now get rdmd's own arguments
    // parse the -o option
    void dashOh(string key, string value)
    {
        if (value[0] == 'f')
        {
            // -ofmyfile passed
            exe = value[1 .. $];
        }
        else if (value[0] == 'd')
        {
            // -odmydir passed
            if(!exe) // Don't let -od override -of
            {
                // add a trailing path separator to clarify it's a dir
                exe = value[1 .. $];
                if (!std.algorithm.endsWith(exe, std.path.sep[]))
                {
                    exe ~= std.path.sep[];
                }
                assert(std.algorithm.endsWith(exe, std.path.sep[]));
            }
        }
        else if (value[0] == '-')
        {
            // -o- passed
            enforce(false, "Option -o- currently not supported by rdmd");
        }
        else
        {
            enforce(false, "Unrecognized option: "~key~value);
        }
    }

    // start the web browser on documentation page
    void man()
    {
        version(Windows)
        {
            // invoke browser that is associated with the http protocol
            ShellExecuteA(null, "open", 
"http://www.digitalmars.com/d/2.0/rdmd.html";, null, null, SW_SHOWNORMAL);
        }
        else
        {        
            foreach (b; [ std.process.getenv("BROWSER"), "firefox",
                            "sensible-browser", "x-www-browser" ]) {
                if (!b.length) continue;

                if (!system(b~" http://www.digitalmars.com/d/2.0/rdmd.html";))
                    return;
            }
        }
    }

        auto programPos = indexOfProgram(args);
        // Insert "--" to tell getopts when to stop
        args = args[0..programPos] ~ "--" ~ args[programPos .. $];

    bool bailout;    // bailout set by functions called in getopt if
                     // program should exit
    string[] loop;       // set by --loop
    bool addStubMain;// set by --main
    string[] eval;     // set by --eval
    bool makeDepend;
    getopt(args,
            std.getopt.config.caseSensitive,
            std.getopt.config.passThrough,
            std.getopt.config.stopOnFirstNonOption,
            "build-only", &buildOnly,
            "chatty", &chatty,
            "compiler", &compiler,
            "dry-run", &dryRun,
            "eval", &eval,
            "loop", &loop,
            "force", &force,
            "help", (string) { writeln(helpString); bailout = true; },
            "main", &addStubMain,
            "makedepend", &makeDepend,
            "man", (string) { man; bailout = true; },
            "o", &dashOh);
    if (bailout) return 0;
    if (dryRun) chatty = true; // dry-run implies chatty

    // Just evaluate this program!
    if (loop)
    {
        return .eval(importWorld ~ "void main(char[][] args) { "
                ~ "foreach (line; stdin.byLine()) {\n"
                ~ std.string.join(loop, "\n")
                ~ ";\n} }");
    }
    if (eval)
    {
        return .eval(importWorld ~ "void main(char[][] args) {\n"
                ~ std.string.join(eval, "\n") ~ ";\n}");
    }

    // Parse the program line - first find the program to run
        programPos = indexOfProgram(args);
    if (programPos == args.length)
    {
        write(helpString);
        return 1;
    }
    auto
        root = /*rel2abs*/(chomp(args[programPos], ".d") ~ ".d"),
        exeBasename = basename(root, ".d"),
        exeDirname = dirname(root),
        programArgs = args[programPos + 1 .. $];
    args = args[0 .. programPos];
    auto compilerFlags = args[1 .. programPos - 1];

    // Compute the object directory and ensure it exists
    immutable objDir = getObjPath(root, compilerFlags);
    // Fetch dependencies
    const myModules = getDependencies(root, objDir, compilerFlags);
    writeln("dependencies returned");

    // --makedepend mode. Just print dependencies and exit.
    if (makeDepend)
    {
        stdout.write(root, " :");
        foreach (mod, _; myModules)
        {
            stdout.write(' ', mod);
        }
        stdout.writeln();
        return 0;
    }

    if (!dryRun)        
    {
        exists(objDir)
            ? enforce(dryRun || isdir(objDir),
                    "Entry `"~objDir~"' exists but is not a directory.")
            : mkdir(objDir);
    }

    // Compute executable name, check for freshness, rebuild
    if (exe)
    {
        // user-specified exe name
        if (std.algorithm.endsWith(exe, std.path.sep[]))
        {
            // user specified a directory, complete it to a file
            exe = std.path.join(exe, exeBasename);
        }
    }
    else
    {
        //exe = exeBasename ~ '.' ~ hash(root, compilerFlags);
        version (Posix)
            exe = std.path.join(myOwnTmpDir, rel2abs(root)[1 .. $])
                ~ '.' ~ hash(root, compilerFlags);
        else version (Windows)
            exe = std.path.join(myOwnTmpDir, replace(root, ".", "-"))
                ~ '-' ~ hash(root, compilerFlags);
        else
            static assert(0);
    }
    // Add an ".exe" for Windows
    exe ~= binExt;

    // Have at it
    if (isNewer(root, exe) ||
            std.algorithm.find!
                ((string a) {return isNewer(a, exe);})
                (myModules.keys).length)
    {
        immutable result = rebuild(root, exe, objDir, myModules, compilerFlags,
                                   addStubMain);
        if (result) return result;
    }

    if (buildOnly)
    {
        // Pretty much done!
        return 0;
    }

    // run
    version (Windows)
    {
        foreach(ref arg; programArgs)
            arg = shellQuote(arg);
        return system(std.string.join([ exe ] ~ programArgs, " "));
    }
    else
    {
        return execv(exe, [ exe ] ~ programArgs);
    }
}

size_t indexOfProgram(string[] args)
{
        foreach(i, arg; args)
        {
                if (i > 0 &&
                !arg.startsWith('-', '@') &&
                !arg.endsWith(".obj", ".o", ".lib", ".a", ".def"))
        {
            return i;
        }
        }
        
        return args.length;
}

bool inALibrary(string source, in string object)
{
    // Heuristics: if source starts with "std.", it's in a library
    return std.string.startsWith(source, "std.")
        || std.string.startsWith(source, "core.")
        || source == "object" || source == "gcstats";
    // another crude heuristic: if a module's path is absolute, it's
    // considered to be compiled in a separate library. Otherwise,
    // it's a source module.
    //return isabs(mod);
}

private string myOwnTmpDir()
{
    version (Posix)
    {
        enum tmpRoot = "/tmp/.rdmd";
    }
    else version (Windows)
    {
        auto tmpRoot = std.process.getenv("TEMP");
        if (!tmpRoot)
        {
            tmpRoot = std.process.getenv("TMP");
        }
        if (!tmpRoot) tmpRoot = std.path.join(".", ".rdmd");
        else tmpRoot ~= sep ~ ".rdmd";
    }
    exists(tmpRoot) && isdir(tmpRoot) || mkdirRecurse(tmpRoot);
    return tmpRoot;
}

private string hash(in string root, in string[] compilerFlags)
{
    enum string[] irrelevantSwitches = [
        "--help", "-ignore", "-quiet", "-v" ];
    MD5_CTX context;
    context.start();
    context.update(getcwd);
    context.update(root);
    foreach (flag; compilerFlags) {
        if (find(irrelevantSwitches, flag).length) continue;
        context.update(flag);
    }
    ubyte digest[16];
    context.finish(digest);
    return digestToString(digest);
}

private string getObjPath(in string root, in string[] compilerFlags)
{
    const tmpRoot = myOwnTmpDir;
    return std.path.join(tmpRoot,
            "rdmd-" ~ basename(root) ~ '-' ~ hash(root, compilerFlags));
}

// Rebuild the executable fullExe starting from modules myModules
// passing the compiler flags compilerFlags. Generates one large
// object file.

private int rebuild(string root, string fullExe,
        string objDir, in string[string] myModules,
        string[] compilerFlags, bool addStubMain)
{
    auto todo = std.string.join(compilerFlags, " ")
        ~" -of"~shellQuote(fullExe)
        ~" -od"~shellQuote(objDir)
        ~" -I"~shellQuote(dirname(root))
        ~" "~shellQuote(root)~" ";
    foreach (k; map!(shellQuote)(myModules.keys)) {
        todo ~= k ~ " ";
    }

    // Need to add void main(){}?
    if (addStubMain)
    {
        auto stubMain = std.path.join(myOwnTmpDir, "stubmain.d");
        std.file.write(stubMain, "void main(){}");
        todo ~= stubMain;
    }

        // Different shells and OS functions have different limits,
        // but 1024 seems to be the smallest maximum outside of MS-DOS.
        enum maxLength = 1024;
        if (todo.length + compiler.length >= maxLength)
        {
                auto rspName = std.path.join(myOwnTmpDir,
                                "rdmd." ~ hash(root, compilerFlags) ~ ".rsp");

                // On Posix, DMD can't handle shell quotes in its response 
files.
                version(Posix)
                {
                        todo = std.string.join(compilerFlags.dup, " ")
                                ~" -of"~fullExe
                                ~" -od"~objDir
                                ~" -I"~dirname(root)
                                ~" "~root~" ";
                        foreach (k; myModules.keys) {
                                todo ~= k ~ " ";
                        }
                }

                std.file.write(rspName, todo);
                todo = shellQuote("@"~rspName);
        }

    immutable result = run(compiler ~ " " ~ todo);
    if (result)
    {
        // build failed
        return result;
    }
    // clean up the dir containing the object file, just not in dry
    // run mode because we haven't created any!
    if (!dryRun) {
        rmdirRecurse(objDir);
    }
    return 0;
}

// Run a program optionally writing the command line first

private int run(string todo)
{
    if (chatty) writeln(todo);
    if (dryRun) return 0;
    return system(todo);
}

// Given module rootModule, returns a mapping of all dependees .d
// source filenames to their corresponding .o files sitting in
// directory objDir. The mapping is obtained by running dmd -v against
// rootModule.

private string[string] getDependencies(string rootModule, string objDir,
        string[] compilerFlags)
{
    string d2obj(string dfile)
    {
        return std.path.join(objDir, chomp(basename(dfile), ".d")~objExt);
    }

    immutable depsFilename = rootModule~".deps";
    immutable rootDir = dirname(rootModule);

    // myModules maps module source paths to corresponding .o names
    string[string] myModules;// = [ rootModule : d2obj(rootModule) ];
    // Must collect dependencies
    immutable depsGetter = /*"cd "~shellQuote(rootDir)~" && "
                             ~*/compiler~" "~std.string.join(compilerFlags, " ")
        ~" -v -o- "~shellQuote(rootModule)
        ~" -I"~shellQuote(rootDir)
        ~" >"~depsFilename;
    if (chatty) writeln(depsGetter);
    immutable depsExitCode = system(depsGetter);
    if (depsExitCode)
    {
        stderr.writeln("Failed: ", depsGetter);
        exit(depsExitCode);
    }
    auto depsReader = File(depsFilename);
    // Leave the deps file in place in case of failure, maybe the user
    // wants to take a look at it
    scope(success) collectException(std.file.remove(depsFilename));
    scope(exit) collectException(depsReader.close); // don't care for errors

    // Fetch all dependent modules and append them to myModules
    auto pattern = new RegExp(r"^import\s+(\S+)\s+\((\S+)\)\s*$");
    foreach (string line; lines(depsReader))
    {
        if (!pattern.test(line)) continue;
        immutable moduleName = pattern[1], moduleSrc = pattern[2];
        if (inALibrary(moduleName, moduleSrc)) continue;
        immutable moduleObj = d2obj(moduleSrc);
        // 2011-06-05: dmd outputs dependencies relative to the path
        // in which the compiler is run, not to the path of the root
        // module. Therefore issue deps relative to current dir.
        //
        //myModules[/*rel2abs*/std.path.join(rootDir, moduleSrc)] = moduleObj;
        myModules[moduleSrc] = moduleObj;
    }

    writeln("returning dependencies");
    return myModules;
}

/*private*/ string shellQuote(string arg)
{
    // This may have to change under windows
    version (Windows) enum quotechar = '"';
    else enum quotechar = '\'';
    version (Windows)
    {
        // Escape trailing backslash, so it doesn't escape the ending quote.
        // Backslashes elsewhere should NOT be escaped.
        if(arg.length > 0 && arg[$-1] == '\\')
            arg ~= '\\';
        arg = std.array.replace(arg, `"`, `\"`);
}
    return quotechar ~ arg ~ quotechar;
}

private bool isNewer(string source, string target)
{
    return force ||
        timeLastModified(source) >= timeLastModified(target, SysTime(0));
}

private string helpString()
{
    return
"rdmd build "~thisVersion~"
Usage: rdmd [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...
Builds (with dependents) and runs a D program.
Example: rdmd -release myprog --myprogparm 5

Any option to be passed to dmd must occur before the program name. In addition
to dmd options, rdmd recognizes the following options:
  --build-only      just build the executable, don't run it
  --chatty          write dmd commands to stdout before executing them
  --compiler=comp   use the specified compiler (e.g. gdmd) instead of dmd
  --dry-run         do not compile, just show what commands would be run
                      (implies --chatty)
  --eval=code       evaluate code \u00E0 la perl -e (multiple --eval allowed)
  --force           force a rebuild even if apparently not necessary
  --help            this message
  --loop            assume \"foreach (line; stdin.byLine()) { ... }\" for eval
  --main            add a stub main program to the mix (e.g. for unittesting)
  --makedepend      print dependencies in makefile format and exit
  --man             open web browser on manual page
  --shebang         rdmd is in a shebang line (put as first argument)
";
}

// For --eval
immutable string importWorld = "
module temporary;
import std.stdio, std.algorithm, std.array, std.base64,
    std.bigint, std.bitmanip, 
    std.compiler, std.complex, std.conv, std.cpuid, std.cstream,
    std.ctype, std.datetime, std.demangle, std.encoding, std.exception, 
    std.file, 
    std.format, std.functional, std.getopt, 
    std.math, std.md5, std.metastrings, std.mmfile,
    std.numeric, std.outbuffer, std.path, std.process, 
    std.random, std.range, std.regex, std.regexp, std.signals, std.socket,
    std.socketstream, std.stdint, std.stdio, std.stdiobase, std.stream,
    std.string, std.syserror, std.system, std.traits, std.typecons,
    std.typetuple, std.uni, std.uri, std.utf, std.variant, std.xml, std.zip,
    std.zlib;
";

int eval(string todo)
{
    MD5_CTX context;
    context.start();
    context.update(todo);
    ubyte digest[16];
    context.finish(digest);
    auto pathname = myOwnTmpDir;
    auto progname = std.path.join(pathname,
            "eval." ~ digestToString(digest));
    auto binName = progname ~ binExt;

    if (exists(binName) ||
            // Compile it
            (std.file.write(progname~".d", todo),
                    run(compiler ~ " " ~ progname ~ ".d -of" ~ binName) == 0))
    {
        // It's there, just run it
        run(binName);
    }

    // Clean pathname
    enum lifetimeInHours = 24;
    auto cutoff = Clock.currTime() - dur!"hours"(lifetimeInHours);
    foreach (DirEntry d; dirEntries(pathname, SpanMode.shallow))
    {
        if (d.timeLastModified < cutoff)
        {
            std.file.remove(d.name);
            //break; // only one per call so we don't waste time
        }
    }

    return 0;
}

string thisVersion()
{
    enum d = __DATE__;
    enum month = d[0 .. 3],
        day = d[4] == ' ' ? "0"~d[5] : d[4 .. 6],
        year = d[7 .. $];
    enum monthNum
        = month == "Jan" ? "01"
        : month == "Feb" ? "02"
        : month == "Mar" ? "03"
        : month == "Apr" ? "04"
        : month == "May" ? "05"
        : month == "Jun" ? "06"
        : month == "Jul" ? "07"
        : month == "Aug" ? "08"
        : month == "Sep" ? "09"
        : month == "Oct" ? "10"
        : month == "Nov" ? "11"
        : month == "Dec" ? "12"
        : "";
    static assert(month != "", "Unknown month "~month);
    return year[0]~year[1 .. $]~monthNum~day;
}

/*
 *  Copyright (C) 2008 by Andrei Alexandrescu
 *  Written by Andrei Alexandrescu, www.erdani.org
 *  Based on an idea by Georg Wrede
 *  Featuring improvements suggested by Christopher Wright
 *  Windows port using bug fixes and suggestions by Adam Ruppe
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  o  The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  o  Altered source versions must be plainly marked as such, and must not
 *     be misrepresented as being the original software.
 *  o  This notice may not be removed or altered from any source
 *     distribution.
 */

Reply via email to