diff --git a/README.md b/README.md index e9e6b8a..0bd1d66 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,29 @@ -dub -=== +dub package manager +=================== -Package and build management system for D \ No newline at end of file +Package and build manager for [D](http://dlang.org/) applications and libraries. + + +Introduction +------------ + +DUB emerged as a more general replacement for [vibe.d's](http://vibed.org/) package manager. It does not imply a dependecy to vibe.d for packages and was extended to not only directly build projects, but also to generate project files (currently [VisualD](https://github.com/rainers/visuald)). + +The project's pilosophy is to keep things as simple as possible. All that is needed to make a project a dub package is to write a short [package.json](http://registry.vibed.org/publish) file and put the source code into a `source` subfolder. It *can* then be registered on the public [package registry](http://registry.vibed.org) to be made available for everyone. Any dependencies specified in `package.json` are automatically downloaded and made available to the project during the build process. + + +Future direction +---------------- + +To make things as flexible as they need to for certain projects, it is planned to gradually add more options to the package file format and eventually to add the possibility to specify an external build tool along with the path of it's output files. The idea is that DUB provides a convenient build management that suffices for 99% of projects, but is also usable as a bare package manager that doesn't get in your way if needed. + + +Installation +------------ + +DUB comes precompiled for Windows, Mac OS, Linux and FreeBSD. It needs to have the following dependencies installed: + + - libevent 2.0.x + - OpenSSL + +The `dub` executable then just needs to be accessible from `PATH` and can be invoked from the root folder of any DUB enabled project to build and run it. diff --git a/bin/dub b/bin/dub deleted file mode 100755 index 38cdf79..0000000 --- a/bin/dub +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -set -e - -# delete old dub.d if another run left it in /tmp -rm -f /tmp/app.d - -# find the executable location (note: must stay mac compatible here) -DUB_BINARY=$(readlink "$0" || true) -if [ ! -n "$DUB_BINARY" ]; then DUB_BINARY="$0"; fi -DUB_BIN=$(dirname "$DUB_BINARY") -DUB_SOURCE=$DUB_BIN/../source -VIBE_SOURCE=$DUB_SOURCE/../../vibe.d/source - -# use pkg-config if possible or fallback to default flags -LIBS=$(pkg-config --libs libevent libevent_pthreads libssl 2>/dev/null || echo "-levent_pthreads -levent -lssl -lcrypto") -LIBS=$(echo "$LIBS" | sed 's/^-L/-L-L/; s/ -L/ -L-L/g; s/^-l/-L-l/; s/ -l/ -L-l/g') -export LIBS - -# generate a file name for the temporary compile/run script -START_SCRIPT=`mktemp -t dub.start.XXXXXXXX` - -# copy dub.d to /tmp and make it deletable by anyone -cp -p "$DUB_SOURCE"/app.d /tmp/app.d -chmod 666 /tmp/app.d - -# run VPM and delete the dub.d file again, VPM will output the compile/run script -rdmd -g -w -property -I"$DUB_SOURCE" -I"$VIBE_SOURCE" $LIBS -Jviews -Isource /tmp/app.d "$VIBE_SOURCE" "$START_SCRIPT" $1 $2 $3 $4 $5 $6 $7 $8 $9 -rm /tmp/app.d - -# compile/run the application -chmod +x "$START_SCRIPT" -"$START_SCRIPT" -rm "$START_SCRIPT" diff --git a/bin/dub.cmd b/bin/dub.cmd deleted file mode 100644 index 0146cca..0000000 --- a/bin/dub.cmd +++ /dev/null @@ -1,22 +0,0 @@ -@echo off -set DUB_BIN=%~dps0 -set DUB_SOURCE=%DUB_BIN%..\source -set VIBE_SOURCE=%DUB_BIN%..\..\vibe.d\source -set LIBDIR=%VIBE_SOURCE%\..\lib\win-i386 -set BINDIR=%DUB_BIN%..\lib\bin -set LIBS="%LIBDIR%\event2.lib" "%LIBDIR%\eay.lib" "%LIBDIR%\ssl.lib" ws2_32.lib -set EXEDIR=%TEMP%\.rdmd\source -set START_SCRIPT=%EXEDIR%\vibe.cmd - -if NOT EXIST %EXEDIR% ( - mkdir %EXEDIR% -) -copy "%DUB_BIN%*.dll" %EXEDIR% > nul 2>&1 -if "%1" == "build" copy "%DUB_BIN%*.dll" . > nul 2>&1 -copy "%DUB_SOURCE%\app.d" %EXEDIR% > nul 2>&1 - -rem Run, execute, do everything.. -rdmd -debug -g -w -property -of%EXEDIR%\dub.exe -I%DUB_SOURCE% -I%VIBE_SOURCE% %LIBS% %EXEDIR%\app.d %VIBE_SOURCE% %START_SCRIPT% %* - -rem Finally, start the app, if dub succeded. -if ERRORLEVEL 0 %START_SCRIPT% diff --git a/bin/libeay32.dll b/bin/libeay32.dll deleted file mode 100644 index 696b300..0000000 --- a/bin/libeay32.dll +++ /dev/null Binary files differ diff --git a/bin/libevent.dll b/bin/libevent.dll deleted file mode 100644 index d2b35db..0000000 --- a/bin/libevent.dll +++ /dev/null Binary files differ diff --git a/bin/ssleay32.dll b/bin/ssleay32.dll deleted file mode 100644 index c0d6d1f..0000000 --- a/bin/ssleay32.dll +++ /dev/null Binary files differ diff --git a/package.json b/package.json index 175de5a..4c612e8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,6 @@ "Sönke Ludwig" ], "dependencies": { - "vibe.d" : ">=0.0.0" + "vibe-d": "~master" } } \ No newline at end of file diff --git a/source/app.d b/source/app.d index c95002d..10d66c5 100644 --- a/source/app.d +++ b/source/app.d @@ -3,11 +3,14 @@ Copyright: © 2012 Matthias Dondorff License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Matthias Dondorff + Authors: Matthias Dondorff, Sönke Ludwig */ module app; +import dub.dependency; import dub.dub; +import dub.platform; +import dub.package_; import dub.registry; import vibe.core.file; @@ -21,33 +24,45 @@ import std.exception; import std.file; import std.getopt; -import std.process; +import stdx.process; int main(string[] args) { string cmd; - try { - if( args.length < 3 ){ - logError("Usage: %s [] [args...] [-- [applicatio args]]\n", args[0]); - // vibe-binary-path: the installation folder of the vibe installation - // start-script-output-file: destination of the script, which can be used to run the app - return 1; - } + version(Windows){ + // rdmd uses $TEMP to compute a temporary path. since cygwin substitutes backslashes + // with slashes, this causes OPTLINK to fail (it thinks path segments are options) + // we substitute the other way around here to fix this. + environment["TEMP"] = environment["TEMP"].replace("/", "\\"); + } + try { // parse general options bool verbose, vverbose, quiet, vquiet; bool help, nodeps, annotate; LogLevel loglevel = LogLevel.Info; + string build_type = "debug", build_config; + bool print_platform, print_builds, print_configs; + bool install_system = false, install_local = false; + string install_version; getopt(args, "v|verbose", &verbose, "vverbose", &vverbose, "q|quiet", &quiet, "vquiet", &vquiet, - "h|help", &help, + "h|help", &help, // obsolete "nodeps", &nodeps, - "annotate", &annotate + "annotate", &annotate, + "build", &build_type, + "config", &build_config, + "print-builds", &print_builds, + "print-configs", &print_configs, + "print-platform", &print_platform, + "system", &install_system, + "local", &install_local, + "version", &install_version ); if( vverbose ) loglevel = LogLevel.Trace; @@ -57,40 +72,48 @@ setLogLevel(loglevel); if( loglevel >= LogLevel.Info ) setPlainLogging(true); - // extract the destination paths - enforce(isDir(args[1]), "Specified binary path is not a directory."); - Path vibedDir = Path(args[1]); - Path dstScript = Path(args[2]); - // extract the command - if( args.length > 3 && !args[3].startsWith("-") ){ - cmd = args[3]; - args = args[0] ~ args[4 .. $]; - } else { - cmd = "run"; - args = args[0] ~ args[3 .. $]; - } + if( args.length > 1 && !args[1].startsWith("-") ){ + cmd = args[1]; + args = args[0] ~ args[2 .. $]; + } else cmd = "run"; // contrary to the documentation, getopt does not remove -- if( args.length >= 2 && args[1] == "--" ) args = args[0] ~ args[2 .. $]; - // display help if requested + // display help if requested (obsolete) if( help ){ showHelp(cmd); return 0; } - auto appPath = getcwd(); - string del_exe_file; - string appStartScript; Url registryUrl = Url.parse("http://registry.vibed.org/"); - logDebug("Using vpm registry url '%s'", registryUrl); + logDebug("Using dub registry url '%s'", registryUrl); + + // FIXME: take into account command line flags + BuildPlatform build_platform; + build_platform.platform = determinePlatform(); + build_platform.architecture = determineArchitecture(); + build_platform.compiler = "dmd"; + + if( print_platform ){ + logInfo("Build platform:"); + logInfo(" Compiler: %s", build_platform.compiler); + logInfo(" System: %s", build_platform.platform.join(" ")); + logInfo(" Architecture: %s", build_platform.architecture.join(" ")); + logInfo(""); + } + + Dub dub = new Dub(new RegistryPS(registryUrl)); // handle the command switch( cmd ){ default: enforce(false, "Command is unknown."); assert(false); + case "help": + showHelp(cmd); + break; case "init": string dir = "."; if( args.length >= 2 ) dir = args[1]; @@ -98,53 +121,167 @@ break; case "run": case "build": - Vpm vpm = new Vpm(Path(appPath), new RegistryPS(registryUrl)); - if( !nodeps ){ - logInfo("Checking dependencies in '%s'", appPath); - logDebug("vpm initialized"); - vpm.update(annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None); + dub.loadPackagefromCwd(); + auto def_config = dub.getDefaultConfiguration(build_platform); + if( !build_config.length ) build_config = def_config; + + if( print_builds ){ + logInfo("Available build types:"); + foreach( tp; ["debug", "release", "unittest", "profile"] ) + logInfo(" %s", tp); + logInfo(""); } + if( print_configs ){ + logInfo("Available configurations:"); + foreach( tp; dub.configurations ) + logInfo(" %s%s", tp, tp == def_config ? " [deault]" : null); + logInfo(""); + } + + if( !nodeps ){ + logInfo("Checking dependencies in '%s'", dub.projectPath); + logDebug("dub initialized"); + dub.update(annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None); + } + + enforce(build_config.length == 0 || dub.configurations.canFind(build_config), "Unknown build configuration: "~build_config); + //Added check for existance of [AppNameInPackagejson].d //If exists, use that as the starting file. - auto outfile = getBinName(vpm); - auto mainsrc = getMainSourceFile(vpm); + auto outfile = getBinName(dub); + auto mainsrc = getMainSourceFile(dub); logDebug("Application output name is '%s'", outfile); // Create start script, which will be used by the calling bash/cmd script. // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments // or with "/" instead of "\" - string[] flags = ["--force"]; + string[] flags = ["--force", "--build-only"]; + Path run_exe_file; if( cmd == "build" ){ - flags ~= "--build-only"; - flags ~= "-of"~outfile; + flags ~= "-of"~(dub.binaryPath~outfile).toNativeString(); } else { - version(Windows){ - import std.random; - auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; - del_exe_file = environment.get("TEMP")~"\\.rdmd\\source\\"~rnd~outfile; - flags ~= "-of"~del_exe_file; + import std.random; + auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; + auto tmp = environment.get("TEMP"); + if( !tmp.length ) tmp = environment.get("TMP"); + if( !tmp.length ){ + version(Posix) tmp = "/tmp"; + else tmp = "."; + } + run_exe_file = Path(tmp~"/.rdmd/source/"~rnd~outfile); + flags ~= "-of"~run_exe_file.toNativeString(); + } + + auto settings = dub.getBuildSettings(build_platform, build_config); + settings.addDFlags(["-w", "-property"]); + settings.addVersions(getPackagesAsVersion(dub)); + + // TODO: this belongs to the builder/generator + if( settings.libs.length ){ + try { + logDebug("Trying to use pkg-config to resolve library flags for %s.", settings.libs); + auto libflags = execute("pkg-config", "--libs" ~ settings.libs.map!(l => "lib"~l)().array()); + enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status)); + settings.addLFlags(libflags.output.split()); + settings.libs = null; + } catch( Exception e ){ + logDebug("pkg-config failed: %s", e.msg); + logDebug("Falling back to direct -lxyz flags."); + version(Windows) settings.addDFlags(settings.libs.map!(l => l~".lib")().array()); + else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); + settings.libs = null; } } - flags ~= "-g"; - flags ~= "-I" ~ (vibedDir ~ ".." ~ "source").toNativeString(); - flags ~= "-Isource"; - flags ~= "-Jviews"; - flags ~= vpm.dflags; - flags ~= getLibs(vibedDir); - flags ~= getPackagesAsVersion(vpm); - flags ~= (mainsrc).toNativeString(); - flags ~= args[1 .. $]; - appStartScript = "rdmd " ~ getDflags() ~ " " ~ join(flags, " "); - if( del_exe_file.length ) appStartScript ~= "\r\ndel \""~del_exe_file~"\""; + flags ~= settings.dflags; + flags ~= settings.lflags.map!(f => "-L"~f)().array(); + flags ~= settings.importPaths.map!(f => "-I"~f)().array(); + flags ~= settings.stringImportPaths.map!(f => "-J"~f)().array(); + flags ~= settings.versions.map!(f => "-version="~f)().array(); + flags ~= settings.files; + flags ~= (mainsrc).toNativeString(); + + string dflags = environment.get("DFLAGS"); + if( dflags ){ + build_type = "$DFLAGS"; + } else { + switch( build_type ){ + default: throw new Exception("Unknown build configuration: "~build_type); + case "plain": dflags = ""; break; + case "debug": dflags = "-g -debug"; break; + case "release": dflags = "-release -O -inline"; break; + case "unittest": dflags = "-g -unittest"; break; + case "profile": dflags = "-g -O -inline -profile"; break; + case "docs": assert(false, "docgen not implemented"); + } + } + + if( build_config.length ) logInfo("Building configuration "~build_config~", build type "~build_type); + else logInfo("Building default configuration, build type "~build_type); + + logInfo("Running %s", "rdmd " ~ dflags ~ " " ~ join(flags, " ")); + auto rdmd_pid = spawnProcess("rdmd " ~ dflags ~ " " ~ join(flags, " ")); + auto result = rdmd_pid.wait(); + enforce(result == 0, "Build command failed with exit code "~to!string(result)); + + if( settings.copyFiles.length ){ + logInfo("Copying files..."); + foreach( f; settings.copyFiles ){ + auto src = Path(f); + auto dst = (run_exe_file.empty ? dub.binaryPath : run_exe_file.parentPath) ~ Path(f).head; + logDebug(" %s to %s", src.toNativeString(), dst.toNativeString()); + copyFile(src, dst, true); + } + } + + if( cmd == "run" ){ + auto prg_pid = spawnProcess(run_exe_file.toNativeString(), args[1 .. $]); + result = prg_pid.wait(); + remove(run_exe_file.toNativeString()); + foreach( f; settings.copyFiles ) + remove((run_exe_file.parentPath ~ Path(f).head).toNativeString()); + enforce(result == 0, "Program exited with code "~to!string(result)); + } + break; case "upgrade": - logInfo("Upgrading application in '%s'", appPath); - Vpm vpm = new Vpm(Path(appPath), new RegistryPS(registryUrl)); - logDebug("vpm initialized"); - vpm.update(UpdateOptions.Reinstall | (annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None)); + dub.loadPackagefromCwd(); + logInfo("Upgrading project in '%s'", dub.projectPath); + logDebug("dub initialized"); + dub.update(UpdateOptions.Reinstall | (annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None)); + break; + case "install": + enforce(args.length >= 2, "Missing package name."); + auto location = InstallLocation.UserWide; + auto name = args[1]; + enforce(!install_local || !install_system, "Cannot install locally and system wide at the same time."); + if( install_local ) location = InstallLocation.Local; + else if( install_system ) location = InstallLocation.SystemWide; + if( install_version.length ) dub.install(name, new Dependency(install_version), location); + else { + try dub.install(name, new Dependency(">=0.0.0"), location); + catch(Exception) dub.install(name, new Dependency("~master"), location); + } + break; + case "uninstall": + enforce("Not implemented."); + break; + case "add-local": + enforce(args.length >= 3, "Missing arguments."); + dub.addLocalPackage(args[1], args[2], install_system); + break; + case "remove-local": + enforce(args.length >= 2, "Missing path to package."); + dub.removeLocalPackage(args[1], install_system); + break; + case "list-locals": + logInfo("Locals:"); + foreach( p; dub.packageManager.getPackageIterator() ) + if( p.installLocation == InstallLocation.Local ) + logInfo(" %s %s: %s", p.name, p.ver, p.path.toNativeString()); + logInfo(""); break; case "generate": string ide; @@ -161,73 +298,66 @@ return 0; } - auto script = openFile(to!string(dstScript), FileMode.CreateTrunc); - scope(exit) script.close(); - script.write(appStartScript); - return 0; } catch(Throwable e) { - logError("Error executing command '%s': %s\n", cmd, e.msg); + logError("Error: %s\n", e.msg); logDebug("Full exception: %s", sanitizeUTF8(cast(ubyte[])e.toString())); - showHelp(cmd); - return -1; + logInfo("Run 'dub help' for usage information."); + return 1; } } - private void showHelp(string command) { // This help is actually a mixup of help for this application and the // supporting vibe script / .cmd file. logInfo( -"Usage: vibe [] [] [-- ] +`Usage: dub [] [] [-- ] -Manages the vibe.d application in the current directory. A single -- can be used -to separate vibe options from options passed to the application. +Manages the DUB project in the current directory. "--" can be used to separate +DUB options from options passed to the application. Possible commands: + help Prints this help screen init [] Initializes an empy project in the specified directory - run Compiles and runs the application + run Compiles and runs the application (default command) build Just compiles the application in the project directory upgrade Forces an upgrade of all dependencies + install Manually installs a package + uninstall Uninstalls a package + add-local + Adds a local package directory (e.g. a git repository) + remove-local Removes a local package directory + list-locals Prints a list of all locals generate Generates project files for a specified IDE. -Options: +General options: + --annotate Do not execute dependency installations, just print -v --verbose Also output debug messages --vverbose Also output trace messages (produces a lot of output) -q --quiet Only output warnings and errors --vquiet No output - -h --help Print this help screen + +Build/run options: + --build=NAME Specifies the type of build to perform. Valid names: + debug (default), release, unittest, profile, docs, + plain + --config=NAME Builds the specified configuration. Configurations can + be defined in package.json --nodeps Do not check dependencies for 'run' or 'build' - --annotate Do not execute dependency installations, just print -"); -} + --print-builds Prints the list of available build types + --print-configs Prints the list of available configurations + --print-platform Prints the identifiers for the current build platform + as used for the build fields in package.json +Install options: + --version Use the specified version/branch instead of the latest + --system Install system wide instead of user local + --local Install as in a sub folder of the current directory -private string getDflags() -{ - auto globVibedDflags = environment.get("DFLAGS"); - if(globVibedDflags == null) - globVibedDflags = "-debug -g -w -property"; - return globVibedDflags; -} - -private string[] getLibs(Path vibedDir) -{ - version(Windows) - { - auto libDir = vibedDir ~ "..\\lib\\win-i386"; - return ["ws2_32.lib", - (libDir ~ "event2.lib").toNativeString(), - (libDir ~ "eay.lib").toNativeString(), - (libDir ~ "ssl.lib").toNativeString()]; - } - version(Posix) - { - return split(environment.get("LIBS", "-L-levent_openssl -L-levent")); - } +`); } private string stripDlangSpecialChars(string s) @@ -239,33 +369,28 @@ return to!string(ret); } -private string[] getPackagesAsVersion(const Vpm vpm) +private string[] getPackagesAsVersion(const Dub dub) { string[] ret; - string[string] pkgs = vpm.installedPackages(); + string[string] pkgs = dub.installedPackages(); foreach(id, vers; pkgs) - ret ~= "-version=VPM_package_" ~ stripDlangSpecialChars(id); + ret ~= "Have_" ~ stripDlangSpecialChars(id); return ret; } -private string getBinName(const Vpm vpm) +private string getBinName(const Dub dub) { - string ret; - if( vpm.packageName.length > 0 ) - ret = vpm.packageName(); - //Otherwise fallback to source/app.d - else ret ="app"; + // take the project name as the base or fall back to "app" + string ret = dub.projectName; + if( ret.length == 0 ) ret ="app"; version(Windows) { ret ~= ".exe"; } - return ret; } -private Path getMainSourceFile(const Vpm vpm) +private Path getMainSourceFile(const Dub dub) { - auto p = Path("source") ~ (vpm.packageName() ~ ".d"); + auto p = Path("source") ~ (dub.projectName ~ ".d"); return existsFile(p) ? p : Path("source/app.d"); - - } private void initDirectory(string fName) @@ -316,13 +441,23 @@ logInfo("Edit source/app.d to start your project."); } `; - + //Make sure we do not overwrite anything accidentally + if( (existsFile(cwd ~ PackageJsonFilename)) || + (existsFile(cwd ~ "source" )) || + (existsFile(cwd ~ "views" )) || + (existsFile(cwd ~ "public" ))) + { + logInfo("The current directory is not empty.\n" + "vibe init aborted."); + //Exit Immediately. + return; + } //Create the common directories. createDirectory(cwd ~ "source"); createDirectory(cwd ~ "views" ); createDirectory(cwd ~ "public"); //Create the common files. - openFile(cwd ~ "package.json", FileMode.Append).write(packageJson); + openFile(cwd ~ PackageJsonFilename, FileMode.Append).write(packageJson); openFile(cwd ~ "source/app.d", FileMode.Append).write(appFile); //Act smug to the user. logInfo("Successfully created empty project."); diff --git a/source/dub/dependency.d b/source/dub/dependency.d index bfab5f6..4eea5f8 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -3,7 +3,7 @@ Copyright: © 2012 Matthias Dondorff License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Matthias Dondorff + Authors: Matthias Dondorff, Sönke Ludwig */ module dub.dependency; @@ -25,17 +25,6 @@ static import std.compiler; -Dependency[string] dependencies(const Json json) -{ - if( "dependencies" !in json ) return null; - Dependency[string] dep; - foreach( string pkg, ref const Json vers; json["dependencies"] ) { - enforce( pkg !in dep, "The dependency '"~pkg~"' is specified more than once." ); - dep[pkg] = new Dependency(cast(string)vers); - } - return dep; -} - /** A version in the format "major.update.bugfix". */ @@ -52,45 +41,38 @@ size_t[] v; } - this(string vers) { - enforce( vers == MASTER_STRING || count(vers, ".") == 2); + this(string vers) + { + enforce(vers == MASTER_STRING || count(vers, ".") == 2); if(vers == MASTER_STRING) { - v = new size_t[3]; - v[0] = v[1] = v[2] = MASTER_VERS; - } - else { - string[] tkns = split(vers, "."); - v = new size_t[tkns.length]; - for(size_t i=0; i other.v[i] ) - return 1; - return 0; + int opCmp(ref const Version other) + const { + foreach( i; 0 .. min(v.length, other.v.length) ) + if( v[i] != other.v[i] ) + return cast(int)v[i] - cast(int)other.v[i]; + return cast(int)v.length - cast(int)other.v.length; } + int opCmp(in Version other) const { return opCmp(other); } - string toString() const { + string toString() + const { enforce( v.length == 3 && (v[0] != MASTER_VERS || v[1] == v[2] && v[1] == MASTER_VERS) ); if(v[0] == MASTER_VERS) return MASTER_STRING; @@ -107,6 +89,13 @@ /// compare methode, e.g. '>=1.0.0 <2.0.0' (i.e. a space separates the two /// version numbers) class Dependency { + private { + string m_cmpA; + Version m_versA; + string m_cmpB; + Version m_versB; + } + this( string ves ) { enforce( ves.length > 0); string orig = ves; @@ -137,9 +126,9 @@ m_cmpB = "<="; } } else { - enforce( ves[idx2+1] == ' ' ); + assert(ves[idx2] == ' '); m_versA = Version(ves[0..idx2]); - string v2 = ves[idx2+2..$]; + string v2 = ves[idx2+1..$]; m_cmpB = skipComp(v2); m_versB = Version(v2); @@ -151,6 +140,13 @@ } } } + + this(string cmp, string ver) + { + m_cmpA = cmp; + m_versB = m_versA = Version(ver); + m_cmpB = "=="; + } this(const Dependency o) { m_cmpA = o.m_cmpA; m_versA = Version(o.m_versA); @@ -162,8 +158,10 @@ override string toString() const { string r; // Special "==" case - if( m_versA == m_versB && m_cmpA == ">=" && m_cmpB == "<=" ) r = "==" ~ to!string(m_versA); - else { + if( m_versA == m_versB && m_cmpA == ">=" && m_cmpB == "<=" ){ + if( m_versA == Version.MASTER ) r = "~master"; + else r = "==" ~ to!string(m_versA); + } else { if( m_versA != Version.RELEASE ) r = m_cmpA ~ to!string(m_versA); if( m_versB != Version.HEAD ) r ~= (r.length==0?"" : " ") ~ m_cmpB ~ to!string(m_versB); if( m_versA == Version.RELEASE && m_versB == Version.HEAD ) r = ">=0.0.0"; @@ -225,7 +223,7 @@ private static string skipComp(ref string c) { size_t idx = 0; while( idx < c.length && !isDigit(c[idx]) ) idx++; - enforce( idx < c.length ); + enforce(idx < c.length, "Expected version number in version spec: "~c); string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx]; c = c[idx..$]; switch(cmp) { @@ -237,21 +235,16 @@ } private static bool doCmp(string mthd, ref const Version a, ref const Version b) { - enforce( mthd==">=" || mthd==">" || mthd=="<=" || mthd=="<"); //logTrace("Calling %s%s%s", a, mthd, b); switch(mthd) { - case ">=": return a>=b; case ">": return a>b; - case "<=": return a<=b; case "<": return a": return a>b; + case ">=": return a>=b; + case "==": return a==b; + case "<=": return a<=b; + case "<": return a=0.0.1 <=0.0.5" and "<= 0.0.4" get ">=0.0.1 <=0.0.4") + // conflicts would then also be detected. + void collectDependenciesRec(Package pack) + { + logDebug("Collecting dependencies for %s", pack.name); + foreach( ldef; pack.localPackageDefs ){ + Path path = ldef.path; + if( !path.absolute ) path = pack.path ~ path; + logDebug("Adding local %s %s", path, ldef.version_); + m_packageManager.addLocalPackage(path, ldef.version_, LocalPackageType.temporary); + } + + foreach( name, vspec; pack.dependencies ){ + auto p = m_packageManager.getBestPackage(name, vspec); + if( !m_dependencies.canFind(p) ){ + logDebug("Found dependency %s %s: %s", name, vspec.toString(), p !is null); + if( p ){ + m_dependencies ~= p; + collectDependenciesRec(p); } } + //enforce(p !is null, "Failed to resolve dependency "~name~" "~vspec.toString()); } } + collectDependenciesRec(m_main); } /// Returns the applications name. @property string name() const { return m_main ? m_main.name : "app"; } - /// Returns the DFLAGS - @property string[] dflags() const { - auto ret = appender!(string[])(); - if( m_main ) ret.put(m_main.dflags()); - ret.put("-Isource"); - ret.put("-Jviews"); - foreach( string s, pkg; m_packages ){ - void addPath(string prefix, string name){ - auto path = "modules/"~pkg.name~"/"~name; - if( exists(path) ) - ret.put(prefix ~ path); - } - ret.put(pkg.dflags()); - addPath("-I", "source"); - addPath("-J", "views"); + @property string[] configurations() + const { + string[] ret; + if( m_main ) ret = m_main.configurations; + foreach( p; m_dependencies ){ + auto cfgs = p.configurations; + foreach( c; cfgs ) + if( !ret.canFind(c) ) ret ~= c; } - return ret.data(); + return ret; + } + + /// Returns the DFLAGS + BuildSettings getBuildSettings(BuildPlatform platform, string config) + const { + BuildSettings ret; + + void addImportPath(string path, bool src) + { + if( !exists(path) ) return; + if( src ) ret.addImportDirs([path]); + else ret.addStringImportDirs([path]); + } + + if( m_main ) processVars(ret, ".", m_main.getBuildSettings(platform, config)); + addImportPath("source", true); + addImportPath("views", false); + + foreach( pkg; m_dependencies ){ + processVars(ret, pkg.path.toNativeString(), pkg.getBuildSettings(platform, config)); + addImportPath((pkg.path ~ "source").toNativeString(), true); + addImportPath((pkg.path ~ "views").toNativeString(), false); + } + + return ret; } /// Actions which can be performed to update the application. - Action[] actions(PackageSupplier packageSupplier, int option) { - scope(exit) writeVpmJson(); + Action[] determineActions(PackageSupplier packageSupplier, int option) { + scope(exit) writeDubJson(); if(!m_main) { Action[] a; @@ -186,14 +256,19 @@ // Gather installed Package[string] installed; installed[m_main.name] = m_main; - foreach(string pkg, ref Package p; m_packages) { - enforce( pkg !in installed, "The package '"~pkg~"' is installed more than once." ); - installed[pkg] = p; + foreach(ref Package p; m_dependencies) { + if( auto ppo = p.name in installed ){ + logError("The same package is referenced in different paths:"); + logError(" %s %s: %s", ppo.name, ppo.vers, ppo.path.toNativeString()); + logError(" %s %s: %s", p.name, p.vers, p.path.toNativeString()); + throw new Exception("Conflicting package multi-references."); + } + installed[p.name] = p; } // To see, which could be uninstalled Package[string] unused = installed.dup; - unused.remove( m_main.name ); + unused.remove(m_main.name); // Check against installed and add install actions Action[] actions; @@ -208,9 +283,14 @@ } else { logDebug("Required package '"~pkg~"' found with version '"~p.vers~"'"); if( option & UpdateOptions.Reinstall ) { - Dependency[string] em; - uninstalls ~= Action( Action.ActionId.Uninstall, pkg, new Dependency("==" ~ p.vers), em); - actions ~= Action(Action.ActionId.InstallUpdate, pkg, d.dependency, d.packages); + if( p.installLocation != InstallLocation.Local ){ + Dependency[string] em; + if( p.installLocation == InstallLocation.ProjectLocal ) + uninstalls ~= Action(Action.ActionId.Uninstall, *p, em); + actions ~= Action(Action.ActionId.InstallUpdate, pkg, d.dependency, d.packages); + } else { + logInfo("Skipping local package %s at %s", p.name, p.path.toNativeString()); + } } if( (pkg in unused) !is null ) @@ -222,7 +302,7 @@ foreach( string pkg, p; unused ) { logDebug("Superfluous package found: '"~pkg~"', version '"~p.vers~"'"); Dependency[string] em; - uninstalls ~= Action( Action.ActionId.Uninstall, pkg, new Dependency("==" ~ p.vers), em); + uninstalls ~= Action( Action.ActionId.Uninstall, pkg, new Dependency("==", p.vers), em); } // Ugly "uninstall" comes first @@ -235,7 +315,7 @@ assert(false); // not properly implemented /* string[] ignores; - auto ignoreFile = to!string(m_root~"vpm.ignore.txt"); + auto ignoreFile = to!string(m_root~"dub.ignore.txt"); if(exists(ignoreFile)){ auto iFile = openFile(ignoreFile); scope(exit) iFile.close(); @@ -249,7 +329,7 @@ ignores ~= ".hg/*"; logDebug("The '%s' file was not found, defaulting to ignore:", ignoreFile); } - ignores ~= "modules/*"; // modules will not be included + ignores ~= ".dub/*"; // .dub will not be included foreach(string i; ignores) logDebug(" " ~ i); @@ -280,19 +360,18 @@ */ } - private { - bool gatherMissingDependencies(PackageSupplier packageSupplier, DependencyGraph graph) { - RequestedDependency[string] missing = graph.missing(); - RequestedDependency[string] oldMissing; - while( missing.length > 0 ) { - if(missing.length == oldMissing.length) { - bool different = false; - foreach(string pkg, reqDep; missing) { - auto o = pkg in oldMissing; - if(o && reqDep.dependency != o.dependency) { - different = true; - break; - } + private bool gatherMissingDependencies(PackageSupplier packageSupplier, DependencyGraph graph) { + RequestedDependency[string] missing = graph.missing(); + RequestedDependency[string] oldMissing; + while( missing.length > 0 ) { + logTrace("Try to resolve %s", missing.keys); + if( missing.keys == oldMissing.keys ){ // FIXME: should actually compare the complete AA here + bool different = false; + foreach(string pkg, reqDep; missing) { + auto o = pkg in oldMissing; + if(o && reqDep.dependency != o.dependency) { + different = true; + break; } if(!different) { logWarn("Could not resolve dependencies"); @@ -308,82 +387,68 @@ continue; } - // TODO: auto update and update interval by time - logTrace("Adding package to graph: "~pkg); - Package p = null; + // TODO: auto update and update interval by time + logTrace("Adding package to graph: "~pkg); + Package p = m_packageManager.getBestPackage(pkg, reqDep.dependency); + if( p ) logTrace("Found installed package %s %s", pkg, p.ver); - // Try an already installed package first - if(!needsUpToDateCheck(pkg)) { - try { - auto json = jsonFromFile( m_root ~ Path("modules") ~ Path(pkg) ~ "package.json"); - auto vers = Version(json["version"].get!string); - if( reqDep.dependency.matches( vers ) ) - p = new Package(json); - logTrace("Using already installed package with version: %s", vers); - } - catch(Throwable e) { - // not yet installed, try the supplied PS - logTrace("An installed package was not found"); - } - } - if(!p) { - try { - p = new Package(packageSupplier.packageJson(pkg, reqDep.dependency)); - logTrace("using package from registry"); - markUpToDate(pkg); - } - catch(Throwable e) { - logError("Geting package metadata for %s failed, exception: %s", pkg, e.toString()); - } - } + // Try an already installed package first + if( p && p.installLocation != InstallLocation.Local && needsUpToDateCheck(pkg) ){ + logInfo("Triggering update of package %s", pkg); + p = null; if(p) graph.insert(p); } - graph.clearUnused(); - missing = graph.missing(); + + if( !p ){ + try { + logDebug("using package from registry"); + p = new Package(packageSupplier.packageJson(pkg, reqDep.dependency)); + markUpToDate(pkg); + } + catch(Throwable e) { + logError("Geting package metadata for %s failed, exception: %s", pkg, e.toString()); + } + } + + if(p) + graph.insert(p); } - return true; + return true; + } + + private bool needsUpToDateCheck(string packageId) { + try { + auto time = m_json["lastUpdate"].opt!(Json[string]).get(packageId, Json("")).get!string; + if( !time.length ) return true; + return (Clock.currTime() - SysTime.fromISOExtString(time)) > dur!"days"(1); + } catch(Exception t) return true; + } + create(m_json, "dub"); + create(m_json["dub"], "lastUpdate"); + m_json["dub"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() ); - bool needsUpToDateCheck(string packageId) { - try { - auto time = m_json["vpm"]["lastUpdate"][packageId].to!string; - return (Clock.currTime() - SysTime.fromISOExtString(time)) > dur!"days"(1); - } - catch(Throwable t) { - return true; - } - } + writeDubJson(); + } - void markUpToDate(string packageId) { - logTrace("markUpToDate(%s)", packageId); - Json create(ref Json json, string object) { - if( object !in json ) json[object] = Json.EmptyObject; - return json[object]; - } - create(m_json, "vpm"); - create(m_json["vpm"], "lastUpdate"); - m_json["vpm"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() ); + private void writeDubJson() { + // don't bother to write an empty file + if( m_json.length == 0 ) return; - writeVpmJson(); - } - - void writeVpmJson() { - // don't bother to write an empty file - if( m_json.length == 0 ) return; - - try { - logTrace("writeVpmJson"); - auto dstFile = openFile((m_root~"vpm.json").toString(), FileMode.CreateTrunc); - scope(exit) dstFile.close(); - Appender!string js; - toPrettyJson(js, m_json); - dstFile.write( js.data ); - } catch( Exception e ){ - logWarn("Could not write vpm.json."); - } + try { + logTrace("writeDubJson"); + auto dubpath = m_root~".dub"; + if( !exists(dubpath.toNativeString()) ) mkdir(dubpath.toNativeString()); + auto dstFile = openFile((dubpath~"dub.json").toString(), FileMode.CreateTrunc); + scope(exit) dstFile.close(); + Appender!string js; + toPrettyJson(js, m_json); + dstFile.write( js.data ); + } catch( Exception e ){ + logWarn("Could not write .dub/dub.json."); } } } @@ -403,24 +468,41 @@ Reinstall = 1<<1 }; -/// The Vpm or Vibe Package Manager helps in getting the applications +/// The Dub class helps in getting the applications /// dependencies up and running. An instance manages one application. -class Vpm { +class Dub { private { + Path m_cwd, m_tempPath; Path m_root; - Application m_app; PackageSupplier m_packageSupplier; + Path m_userDubPath, m_systemDubPath; + Json m_systemConfig, m_userConfig; + PackageManager m_packageManager; + Application m_app; PackageStore m_packageStore; } /// Initiales the package manager for the vibe application /// under root. - this(Path root, PackageSupplier ps = defaultPackageSupplier()) { - enforce(root.absolute, "Specify an absolute path for the VPM"); - m_root = root; + this(PackageSupplier ps = defaultPackageSupplier()) + { + m_cwd = Path(getcwd()); + + version(Windows){ + m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/"; + m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/"; + m_tempPath = Path(environment.get("TEMP")); + } else version(Posix){ + m_systemDubPath = Path("/etc/dub/"); + m_userDubPath = Path(environment.get("HOME")) ~ ".dub/"; + m_tempPath = Path("/tmp"); + } + + m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true); + m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true); + m_packageSupplier = ps; - m_packageStore = new PackageStore(); - m_app = new Application(root); + m_packageManager = new PackageManager(m_systemDubPath ~ "packages/", m_userDubPath ~ "packages/"); /// HACK m_packageStore.includePath(Path("E:\\dev\\")); @@ -429,11 +511,28 @@ /// Returns the name listed in the package.json of the current /// application. - @property string packageName() const { return m_app.name; } + @property string projectName() const { return m_app.name; } + + @property Path projectPath() const { return m_root; } + + @property string[] configurations() const { return m_app.configurations; } + + @property inout(PackageManager) packageManager() inout { return m_packageManager; } + + @property Path binaryPath() const { return m_app.binaryPath; } + + void loadPackagefromCwd() + { + m_root = m_cwd; + m_packageManager.projectPackagePath = m_root ~ ".dub/packages/"; + m_app = new Application(m_packageManager, m_root); + } /// Returns a list of flags which the application needs to be compiled /// properly. - @property string[] dflags() { return m_app.dflags; } + BuildSettings getBuildSettings(BuildPlatform platform, string config) { return m_app.getBuildSettings(platform, config); } + + string getDefaultConfiguration(BuildPlatform platform) const { return m_app.getDefaultConfiguration(platform); } /// Lists all installed modules void list() { @@ -444,7 +543,7 @@ /// the application. /// @param options bit combination of UpdateOptions bool update(UpdateOptions options) { - Action[] actions = m_app.actions(m_packageSupplier, options); + Action[] actions = m_app.determineActions(m_packageSupplier, options); if( actions.length == 0 ) return true; logInfo("The following changes could be performed:"); @@ -470,14 +569,16 @@ // foreach(Action a; filter!((Action a) => a.action == Action.ActionId.InstallUpdate)(actions)) // install(a.packageId, a.vers); foreach(Action a; actions) - if(a.action == Action.ActionId.Uninstall) - uninstall(a.packageId); + if(a.action == Action.ActionId.Uninstall){ + assert(a.pack !is null, "No package specified for uninstall."); + uninstall(a.pack); + } foreach(Action a; actions) if(a.action == Action.ActionId.InstallUpdate) install(a.packageId, a.vers); m_app.reinit(); - Action[] newActions = m_app.actions(m_packageSupplier, 0); + Action[] newActions = m_app.determineActions(m_packageSupplier, 0); if(newActions.length > 0) { logInfo("There are still some actions to perform:"); foreach(Action a; newActions) @@ -506,145 +607,104 @@ /// Installs the package matching the dependency into the application. /// @param addToApplication if true, this will also add an entry in the /// list of dependencies in the application's package.json - void install(string packageId, const Dependency dep, bool addToApplication = false) { - logInfo("Installing "~packageId~"..."); - auto destination = m_root ~ "modules" ~ packageId; - if(exists(to!string(destination))) - throw new Exception(packageId~" needs to be uninstalled prior installation."); + void install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.ProjectLocal) + { + auto pinfo = m_packageSupplier.packageJson(packageId, dep); + string ver = pinfo["version"].get!string; - // download - ZipArchive archive; - { - logDebug("Aquiring package zip file"); - auto dload = m_root ~ "temp/downloads"; - if(!exists(to!string(dload))) - mkdirRecurse(to!string(dload)); - auto tempFile = m_root ~ ("temp/downloads/"~packageId~".zip"); - string sTempFile = to!string(tempFile); - if(exists(sTempFile)) remove(sTempFile); - m_packageSupplier.storePackage(tempFile, packageId, dep); // Q: continue on fail? - scope(exit) remove(sTempFile); + logInfo("Installing %s %s...", packageId, ver); - // unpack - auto f = openFile(to!string(tempFile), FileMode.Read); - scope(exit) f.close(); - ubyte[] b = new ubyte[cast(uint)f.leastSize]; - f.read(b); - archive = new ZipArchive(b); - } + logDebug("Aquiring package zip file"); + auto dload = m_root ~ ".dub/temp/downloads"; + auto tempFile = m_tempPath ~ ("dub-download-"~packageId~"-"~ver~".zip"); + string sTempFile = to!string(tempFile); + if(exists(sTempFile)) remove(sTempFile); + m_packageSupplier.storePackage(tempFile, packageId, dep); // Q: continue on fail? + scope(exit) remove(sTempFile); - Path getPrefix(ZipArchive a) { - foreach(ArchiveMember am; a.directory) - if( Path(am.name).head == PathEntry("package.json") ) - return Path(am.name).parentPath; - - // not correct zip packages HACK - Path minPath; - foreach(ArchiveMember am; a.directory) - if( isPathFromZip(am.name) && (minPath == Path() || minPath.startsWith(Path(am.name))) ) - minPath = Path(am.name); - - return minPath; - } - - logDebug("Installing from zip."); - - // In a github zip, the actual contents are in a subfolder - auto prefixInPackage = getPrefix(archive); - - Path getCleanedPath(string fileName) { - auto path = Path(fileName); - if(prefixInPackage != Path() && !path.startsWith(prefixInPackage)) return Path(); - return path[prefixInPackage.length..path.length]; - } - - // install - mkdirRecurse(to!string(destination)); - Journal journal = new Journal; - foreach(ArchiveMember a; archive.directory) { - if(!isPathFromZip(a.name)) continue; - - auto cleanedPath = getCleanedPath(a.name); - if(cleanedPath.empty) continue; - auto fileName = to!string(destination~cleanedPath); - - if( exists(fileName) && isDir(fileName) ) continue; - - logDebug("Creating %s", fileName); - mkdirRecurse(fileName); - auto subPath = cleanedPath; - for(size_t i=0; i a.type == Journal.Type.RegularFile)(journal.entries)) { - logTrace("Deleting file '%s'", e.relFilename); - auto absFile = packagePath~e.relFilename; - if(!exists(to!string(absFile))) { - logWarn("Previously installed file not found for uninstalling: '%s'", absFile); - continue; + void addLocalPackage(string path, string ver, bool system) + { + auto abs_path = Path(path); + if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; + m_packageManager.addLocalPackage(abs_path, Version(ver), system ? LocalPackageType.system : LocalPackageType.user); + } + + void removeLocalPackage(string path, bool system) + { + auto abs_path = Path(path); + if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; + m_packageManager.removeLocalPackage(abs_path, system ? LocalPackageType.system : LocalPackageType.user); + } +} + +private void processVars(ref BuildSettings dst, string project_path, BuildSettings settings) +{ + dst.addDFlags(processVars(project_path, settings.dflags)); + dst.addLFlags(processVars(project_path, settings.lflags)); + dst.addLibs(processVars(project_path, settings.libs)); + dst.addFiles(processVars(project_path, settings.files, true)); + dst.addCopyFiles(processVars(project_path, settings.copyFiles, true)); + dst.addVersions(processVars(project_path, settings.versions)); + dst.addImportDirs(processVars(project_path, settings.importPaths, true)); + dst.addStringImportDirs(processVars(project_path, settings.stringImportPaths, true)); +} + +private string[] processVars(string project_path, string[] vars, bool are_paths = false) +{ + auto ret = appender!(string[])(); + processVars(ret, project_path, vars, are_paths); + return ret.data; + +} +private void processVars(ref Appender!(string[]) dst, string project_path, string[] vars, bool are_paths = false) +{ + foreach( var; vars ){ + auto idx = std.string.indexOf(var, '$'); + if( idx >= 0 ){ + auto vres = appender!string(); + while( idx >= 0 ){ + if( idx+1 >= var.length ) break; + if( var[idx+1] == '$' ){ + vres.put(var[0 .. idx+1]); + var = var[idx+2 .. $]; + } else { + vres.put(var[0 .. idx]); + var = var[idx+1 .. $]; + + size_t idx2 = 0; + while( idx2 < var.length && isIdentChar(var[idx2]) ) idx2++; + auto varname = var[0 .. idx2]; + var = var[idx2 .. $]; + + if( varname == "PACKAGE_DIR" ) vres.put(project_path); + else enforce(false, "Invalid variable: "~varname); + } + idx = std.string.indexOf(var, '$'); } - - remove(to!string(absFile)); + vres.put(var); + var = vres.data; } - - logDebug("Erasing directories"); - Path[] allPaths; - foreach(Journal.Entry e; filter!((Journal.Entry a) => a.type == Journal.Type.Directory)(journal.entries)) - allPaths ~= packagePath~e.relFilename; - sort!("a.length>b.length")(allPaths); // sort to erase deepest paths first - foreach(Path p; allPaths) { - logTrace("Deleting folder '%s'", p); - if( !exists(to!string(p)) || !isDir(to!string(p)) || !isEmptyDir(p) ) { - logError("Alien files found, directory is not empty or is not a directory: '%s'", p); - continue; + if( are_paths ){ + auto p = Path(var); + if( !p.absolute ){ + logTrace("Fixing relative path: %s ~ %s", project_path, p.toNativeString()); + p = Path(project_path) ~ p; } - rmdir( to!string(p) ); - } - - if(!isEmptyDir(packagePath)) - throw new Exception("Alien files found in '"~to!string(packagePath)~"', manual uninstallation needed."); - - rmdir(to!string(packagePath)); - logInfo("Uninstalled package: '"~packageId~"'"); + dst.put(p.toNativeString()); + } else dst.put(var); } /// Generate project files for a specified IDE. @@ -659,3 +719,8 @@ generator.generateProject(); } } + +private bool isIdentChar(char ch) +{ + return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_'; +} \ No newline at end of file diff --git a/source/dub/package_.d b/source/dub/package_.d index 475dde7..91880cd 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -12,13 +12,99 @@ import std.array; import std.conv; -import std.file; import std.exception; - import vibe.core.file; import vibe.data.json; import vibe.inet.url; +enum PackageJsonFilename = "package.json"; + +struct BuildPlatform { + string[] platform; + string[] architecture; + string compiler; +} + +struct BuildSettings { + string[] dflags; + string[] lflags; + string[] libs; + string[] files; + string[] copyFiles; + string[] versions; + string[] importPaths; + string[] stringImportPaths; + + void parse(in Json root, BuildPlatform platform) + { + addDFlags(getPlatformField(root, "dflags", platform)); + addLFlags(getPlatformField(root, "lflags", platform)); + addLibs(getPlatformField(root, "libs", platform)); + addFiles(getPlatformField(root, "files", platform)); + addCopyFiles(getPlatformField(root, "copyFiles", platform)); + addVersions(getPlatformField(root, "versions", platform)); + addImportDirs(getPlatformField(root, "importPaths", platform)); + addStringImportDirs(getPlatformField(root, "stringImportPaths", platform)); + } + + void addDFlags(string[] value) { add(dflags, value); } + void addLFlags(string[] value) { add(lflags, value); } + void addLibs(string[] value) { add(libs, value); } + void addFiles(string[] value) { add(files, value); } + void addCopyFiles(string[] value) { add(copyFiles, value); } + void addVersions(string[] value) { add(versions, value); } + void addImportDirs(string[] value) { add(importPaths, value); } + void addStringImportDirs(string[] value) { add(stringImportPaths, value); } + + private void add(ref string[] arr, string[] vals) + { + foreach( v; vals ){ + bool found = false; + foreach( i; 0 .. arr.length ) + if( arr[i] == v ){ + found = true; + break; + } + if( !found ) arr ~= v; + } + } + + private string[] getPlatformField(in Json json, string name, BuildPlatform platform) + const { + auto ret = appender!(string[])(); + foreach( suffix; getPlatformSuffixIterator(platform) ){ + foreach( j; json[name~suffix].opt!(Json[]) ) + ret.put(j.get!string); + } + return ret.data; + } +} + +int delegate(scope int delegate(ref string)) getPlatformSuffixIterator(BuildPlatform platform) +{ + int iterator(scope int delegate(ref string s) del) + { + auto c = platform.compiler; + int delwrap(string s) { return del(s); } + if( auto ret = delwrap(null) ) return ret; + if( auto ret = delwrap("-"~c) ) return ret; + foreach( p; platform.platform ){ + if( auto ret = delwrap("-"~p) ) return ret; + if( auto ret = delwrap("-"~p~"-"~c) ) return ret; + foreach( a; platform.architecture ){ + if( auto ret = delwrap("-"~p~"-"~a) ) return ret; + if( auto ret = delwrap("-"~p~"-"~a~"-"~c) ) return ret; + } + } + foreach( a; platform.architecture ){ + if( auto ret = delwrap("-"~a) ) return ret; + if( auto ret = delwrap("-"~a~"-"~c) ) return ret; + } + return 0; + } + return &iterator; +} + /// Representing an installed package // Json file example: // { @@ -31,40 +117,81 @@ // "dependencies": { // "black-sabbath": ">=1.0.0", // "CowboysFromHell": "<1.0.0", -// "BeneathTheRemains": ">=1.0.3" +// "BeneathTheRemains": {"version": "0.4.1", "path": "./beneath-0.4.1"} // } // "licenses": { // ... // } // } class Package { + static struct LocalPacageDef { string name; Version version_; Path path; } + private { + InstallLocation m_location; + Path m_path; Json m_meta; Dependency[string] m_dependencies; - Path m_root; + LocalPacageDef[] m_localPackageDefs; } - - this(Path root) { - m_meta = jsonFromFile(root ~ "package.json"); - m_dependencies = .dependencies(m_meta); + + this(InstallLocation location, Path root) + { + this(jsonFromFile(root ~ PackageJsonFilename), location, root); m_root = root; } - this(Json json) { - m_meta = json; - m_dependencies = .dependencies(m_meta); + + this(Json package_info, InstallLocation location = InstallLocation.Local, Path root = Path()) + { + m_location = location; + m_path = root; + m_meta = package_info; + + // extract dependencies and local package definitions + if( auto pd = "dependencies" in package_info ){ + foreach( string pkg, verspec; *pd ) { + enforce(pkg !in m_dependencies, "The dependency '"~pkg~"' is specified more than once." ); + if( verspec.type == Json.Type.Object ){ + auto ver = verspec["version"].get!string; + m_dependencies[pkg] = new Dependency("==", ver); + m_localPackageDefs ~= LocalPacageDef(pkg, Version(ver), Path(verspec.path.get!string())); + } else m_dependencies[pkg] = new Dependency(verspec.get!string()); + } + } } @property string name() const { return cast(string)m_meta["name"]; } @property string vers() const { return cast(string)m_meta["version"]; } + @property Version ver() const { return Version(m_meta["version"].get!string); } + @property installLocation() const { return m_location; } + @property Path path() const { return m_path; } @property const(Url) url() const { return Url.parse(cast(string)m_meta["url"]); } @property const(Dependency[string]) dependencies() const { return m_dependencies; } - @property string[] dflags() const { - if( "dflags" !in m_meta ) return null; - auto flags = m_meta["dflags"].get!(Json[]); + @property const(LocalPacageDef)[] localPackageDefs() const { return m_localPackageDefs; } + @property string binaryPath() const { return m_meta["binaryPath"].opt!string; } + + @property string[] configurations() + const { + auto pv = "configurations" in m_meta; + if( !pv ) return null; auto ret = appender!(string[])(); - foreach( f; flags ) ret.put(f.get!string); + foreach( string k, _; *pv ) + ret.put(k); return ret.data; } + + BuildSettings getBuildSettings(BuildPlatform platform, string config) + const { + BuildSettings ret; + ret.parse(m_meta, platform); + if( config.length ){ + auto pcs = "configurations" in m_meta; + if( !pcs ) return ret; + auto pc = config in *pcs; + if( !pc ) return ret; + ret.parse(*pc, platform); + } + return ret; + } @property const(Path[]) sources() const { enforce(m_root != Path(), "Cannot assemble sources from package, m_root == Path()."); @@ -75,6 +202,16 @@ return allSources; } + string getDefaultConfiguration(BuildPlatform platform) + const { + string ret; + auto cfgs = m_meta["configurations"].opt!(Json[string]); + foreach( suffix; getPlatformSuffixIterator(platform) ) + if( auto pv = ("default"~suffix) in cfgs ) + ret = pv.get!string(); + return ret; + } + string info() const { string s; s ~= cast(string)m_meta["name"] ~ ", version '" ~ cast(string)m_meta["version"] ~ "'"; @@ -89,10 +226,17 @@ /// Writes the json file back to the filesystem void writeJson(Path path) { - auto dstFile = openFile((path~"package.json").toString(), FileMode.CreateTrunc); + auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc); scope(exit) dstFile.close(); Appender!string js; toPrettyJson(js, m_meta); dstFile.write( js.data ); } } + +enum InstallLocation { + Local, + ProjectLocal, + UserWide, + SystemWide +} diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d new file mode 100644 index 0000000..61dcdd4 --- /dev/null +++ b/source/dub/packagemanager.d @@ -0,0 +1,474 @@ +/** + Management of packages on the local computer. + + Copyright: © 2012 rejectedsoftware e.K. + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Sönke Ludwig, Matthias Dondorff +*/ +module dub.packagemanager; + +import dub.dependency; +import dub.installation; +import dub.package_; +import dub.utils; + +import std.algorithm : countUntil, filter, sort; +import std.conv; +import std.exception; +import std.file; +import std.zip; +import vibe.core.file; +import vibe.core.log; +import vibe.data.json; +import vibe.inet.path; +import vibe.stream.operations; + + +enum JournalJsonFilename = "journal.json"; +enum LocalPackagesFilename = "local-packages.json"; + +enum LocalPackageType { + temporary, + user, + system +} + + +class PackageManager { + private { + Path m_systemPackagePath; + Path m_userPackagePath; + Path m_projectPackagePath; + Package[][string] m_systemPackages; + Package[][string] m_userPackages; + Package[string] m_projectPackages; + Package[] m_localTemporaryPackages; + Package[] m_localUserPackages; + Package[] m_localSystemPackages; + } + + this(Path system_package_path, Path user_package_path, Path project_package_path = Path()) + { + m_systemPackagePath = system_package_path; + m_userPackagePath = user_package_path; + m_projectPackagePath = project_package_path; + refresh(); + } + + @property Path projectPackagePath() const { return m_projectPackagePath; } + @property void projectPackagePath(Path path) { m_projectPackagePath = path; refresh(); } + + Package getPackage(string name, Version ver) + { + foreach( p; getPackageIterator(name) ) + if( p.ver == ver ) + return p; + return null; + } + + Package getBestPackage(string name, string version_spec) + { + return getBestPackage(name, new Dependency(version_spec)); + } + + Package getBestPackage(string name, in Dependency version_spec) + { + Package ret; + foreach( p; getPackageIterator(name) ) + if( version_spec.matches(p.ver) && (!ret || p.ver > ret.ver) ) + ret = p; + return ret; + } + + int delegate(int delegate(ref Package)) getPackageIterator() + { + int iterator(int delegate(ref Package) del) + { + // first search project local packages + foreach( p; m_localTemporaryPackages ) + if( auto ret = del(p) ) return ret; + foreach( p; m_projectPackages ) + if( auto ret = del(p) ) return ret; + + // then local packages + foreach( p; m_localUserPackages ) + if( auto ret = del(p) ) return ret; + + // then local packages + foreach( p; m_localSystemPackages ) + if( auto ret = del(p) ) return ret; + + // then user installed packages + foreach( pl; m_userPackages ) + foreach( v; pl ) + if( auto ret = del(v) ) + return ret; + + // finally system-wide installed packages + foreach( pl; m_systemPackages ) + foreach( v; pl ) + if( auto ret = del(v) ) + return ret; + + return 0; + } + + return &iterator; + } + + int delegate(int delegate(ref Package)) getPackageIterator(string name) + { + int iterator(int delegate(ref Package) del) + { + // first search project local packages + foreach( p; m_localTemporaryPackages ) + if( p.name == name ) + if( auto ret = del(p) ) return ret; + if( auto pp = name in m_projectPackages ) + if( auto ret = del(*pp) ) return ret; + + // then local packages + foreach( p; m_localUserPackages ) + if( p.name == name ) + if( auto ret = del(p) ) return ret; + + // then local packages + foreach( p; m_localSystemPackages ) + if( p.name == name ) + if( auto ret = del(p) ) return ret; + + // then user installed packages + if( auto pp = name in m_userPackages ) + foreach( v; *pp ) + if( auto ret = del(v) ) + return ret; + + // finally system-wide installed packages + if( auto pp = name in m_systemPackages ) + foreach( v; *pp ) + if( auto ret = del(v) ) + return ret; + + return 0; + } + + return &iterator; + } + + Package install(Path zip_file_path, Json package_info, InstallLocation location) + { + auto package_name = package_info.name.get!string(); + auto package_version = package_info["version"].get!string(); + + Path destination; + final switch( location ){ + case InstallLocation.Local: destination = Path(package_name); break; + case InstallLocation.ProjectLocal: enforce(!m_projectPackagePath.empty, "no project path set."); destination = m_projectPackagePath ~ package_name; break; + case InstallLocation.UserWide: destination = m_userPackagePath ~ (package_name ~ "/" ~ package_version); break; + case InstallLocation.SystemWide: destination = m_systemPackagePath ~ (package_name ~ "/" ~ package_version); break; + } + + if( existsFile(destination) ) + throw new Exception(package_name~" needs to be uninstalled prior installation."); + + // open zip file + ZipArchive archive; + { + auto f = openFile(zip_file_path, FileMode.Read); + scope(exit) f.close(); + archive = new ZipArchive(f.readAll()); + } + + logDebug("Installing from zip."); + + // In a github zip, the actual contents are in a subfolder + Path zip_prefix; + foreach(ArchiveMember am; archive.directory) + if( Path(am.name).head == PathEntry(PackageJsonFilename) ){ + zip_prefix = Path(am.name)[0 .. 1]; + break; + } + + if( zip_prefix.empty ){ + // not correct zip packages HACK + Path minPath; + foreach(ArchiveMember am; archive.directory) + if( isPathFromZip(am.name) && (minPath == Path() || minPath.startsWith(Path(am.name))) ) + zip_prefix = Path(am.name); + } + + logDebug("zip root folder: %s", zip_prefix); + + Path getCleanedPath(string fileName) { + auto path = Path(fileName); + if(zip_prefix != Path() && !path.startsWith(zip_prefix)) return Path(); + return path[zip_prefix.length..path.length]; + } + + // install + mkdirRecurse(destination.toNativeString()); + auto journal = new Journal; + foreach(ArchiveMember a; archive.directory) { + auto cleanedPath = getCleanedPath(a.name); + if(cleanedPath.empty) continue; + auto dst_path = destination~cleanedPath; + + logDebug("Creating %s", cleanedPath); + if( dst_path.endsWithSlash ){ + if( !existsDirectory(dst_path) ) + mkdirRecurse(dst_path.toNativeString()); + journal.add(Journal.Entry(Journal.Type.Directory, cleanedPath)); + } else { + if( !existsDirectory(dst_path.parentPath) ) + mkdirRecurse(dst_path.parentPath.toNativeString()); + auto dstFile = openFile(dst_path, FileMode.CreateTrunc); + scope(exit) dstFile.close(); + dstFile.write(archive.expand(a)); + journal.add(Journal.Entry(Journal.Type.RegularFile, cleanedPath)); + } + } + + // overwrite package.json (this one includes a version field) + Json pi = jsonFromFile(destination~PackageJsonFilename); + pi["version"] = package_info["version"]; + writeJsonFile(destination~PackageJsonFilename, pi); + + // Write journal + logTrace("Saving installation journal..."); + journal.add(Journal.Entry(Journal.Type.RegularFile, Path(JournalJsonFilename))); + journal.save(destination ~ JournalJsonFilename); + + if( existsFile(destination~PackageJsonFilename) ) + logInfo("%s has been installed with version %s", package_name, package_version); + + auto pack = new Package(location, destination); + final switch( location ){ + case InstallLocation.Local: break; + case InstallLocation.ProjectLocal: m_projectPackages[package_name] = pack; break; + case InstallLocation.UserWide: m_userPackages[package_name] ~= pack; break; + case InstallLocation.SystemWide: m_systemPackages[package_name] ~= pack; break; + } + return pack; + } + + void uninstall(in Package pack) + { + enforce(!pack.path.empty, "Cannot uninstall package "~pack.name~" without a path."); + + // remove package from package list + final switch(pack.installLocation){ + case InstallLocation.Local: assert(false, "Cannot uninstall locally installed package."); + case InstallLocation.ProjectLocal: + auto pp = pack.name in m_projectPackages; + assert(pp !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed in project."); + assert(*pp is pack); + m_projectPackages.remove(pack.name); + break; + case InstallLocation.UserWide: + auto pv = pack.name in m_systemPackages; + assert(pv !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed in user repository."); + auto idx = countUntil(*pv, pack); + assert(idx < 0 || (*pv)[idx] is pack); + if( idx >= 0 ) *pv = (*pv)[0 .. idx] ~ (*pv)[idx+1 .. $]; + break; + case InstallLocation.SystemWide: + auto pv = pack.name in m_userPackages; + assert(pv !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed system repository."); + auto idx = countUntil(*pv, pack); + assert(idx < 0 || (*pv)[idx] is pack); + if( idx >= 0 ) *pv = (*pv)[0 .. idx] ~ (*pv)[idx+1 .. $]; + break; + } + + // delete package files physically + auto journalFile = pack.path~JournalJsonFilename; + if( !existsFile(journalFile) ) + throw new Exception("Uninstall failed, no journal found for '"~pack.name~"'. Please uninstall manually."); + + auto packagePath = pack.path; + auto journal = new Journal(journalFile); + logDebug("Erasing files"); + foreach( Journal.Entry e; filter!((Journal.Entry a) => a.type == Journal.Type.RegularFile)(journal.entries)) { + logTrace("Deleting file '%s'", e.relFilename); + auto absFile = pack.path~e.relFilename; + if(!existsFile(absFile)) { + logWarn("Previously installed file not found for uninstalling: '%s'", absFile); + continue; + } + + removeFile(absFile); + } + + logDebug("Erasing directories"); + Path[] allPaths; + foreach(Journal.Entry e; filter!((Journal.Entry a) => a.type == Journal.Type.Directory)(journal.entries)) + allPaths ~= pack.path~e.relFilename; + sort!("a.length>b.length")(allPaths); // sort to erase deepest paths first + foreach(Path p; allPaths) { + logTrace("Deleting folder '%s'", p); + if( !existsFile(p) || !isDir(p.toNativeString()) || !isEmptyDir(p) ) { + logError("Alien files found, directory is not empty or is not a directory: '%s'", p); + continue; + } + rmdir(p.toNativeString()); + } + + if(!isEmptyDir(pack.path)) + throw new Exception("Alien files found in '"~pack.path.toNativeString()~"', needs to be deleted manually."); + + rmdir(pack.path.toNativeString()); + logInfo("Uninstalled package: '"~pack.name~"'"); + } + + void addLocalPackage(in Path path, in Version ver, LocalPackageType type) + { + Package[]* packs = getLocalPackageList(type); + auto info = jsonFromFile(path ~ PackageJsonFilename, false); + string name; + if( "name" !in info ) info["name"] = path.head.toString(); + info["version"] = ver.toString(); + + // don't double-add packages + foreach( p; *packs ){ + if( p.path == path ){ + enforce(p.ver == ver, "Adding local twice with different versions is not allowed."); + return; + } + } + + *packs ~= new Package(info, InstallLocation.Local, path); + + writeLocalPackageList(type); + } + + void removeLocalPackage(in Path path, LocalPackageType type) + { + Package[]* packs = getLocalPackageList(type); + size_t[] to_remove; + foreach( i, entry; *packs ) + if( entry.path == path ) + to_remove ~= i; + enforce(to_remove.length > 0, "No "~type.to!string()~" package found at "~path.toNativeString()); + + foreach_reverse( i; to_remove ) + *packs = (*packs)[0 .. i] ~ (*packs)[i+1 .. $]; + + writeLocalPackageList(type); + } + + void refresh() + { + // rescan the system and user package folder + void scanPackageFolder(Path path, ref Package[][string] packs, InstallLocation location) + { + packs = null; + if( path.existsDirectory() ){ + logDebug("iterating dir %s", path.toNativeString()); + try foreach( pdir; iterateDirectory(path) ){ + logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name); + if( !pdir.isDirectory ) continue; + Package[] vers; + auto pack_path = path ~ pdir.name; + foreach( vdir; iterateDirectory(pack_path) ){ + if( !vdir.isDirectory ) continue; + auto ver_path = pack_path ~ vdir.name; + if( !existsFile(ver_path ~ PackageJsonFilename) ) continue; + try { + auto p = new Package(location, ver_path); + vers ~= p; + } catch( Exception e ){ + logError("Failed to load package in %s: %s", ver_path, e.msg); + } + } + packs[pdir.name] = vers; + } + catch(Exception e) logDebug("Failed to enumerate %s packages: %s", to!string(location), e.toString()); + } + } + scanPackageFolder(m_systemPackagePath, m_systemPackages, InstallLocation.SystemWide); + scanPackageFolder(m_userPackagePath, m_userPackages, InstallLocation.UserWide); + + + // rescan the project package folder + m_projectPackages = null; + if( !m_projectPackagePath.empty && m_projectPackagePath.existsDirectory() ){ + logDebug("iterating dir %s", m_projectPackagePath.toNativeString()); + try foreach( pdir; m_projectPackagePath.iterateDirectory() ){ + if( !pdir.isDirectory ) continue; + auto pack_path = m_projectPackagePath ~ pdir.name; + if( !existsFile(pack_path ~ PackageJsonFilename) ) continue; + + try { + auto p = new Package(InstallLocation.ProjectLocal, pack_path); + m_projectPackages[pdir.name] = p; + } catch( Exception e ){ + logError("Failed to load package in %s: %s", pack_path, e.msg); + } + } + catch(Exception e) logDebug("Failed to enumerate project packages: %s", e.toString()); + } + + // load locally defined packages + void scanLocalPackages(Path list_path, ref Package[] packs){ + try { + logDebug("Looking for local package map at %s", list_path.toNativeString()); + if( !existsFile(list_path ~ LocalPackagesFilename) ) return; + logDebug("Try to load local package map at %s", list_path.toNativeString()); + auto packlist = jsonFromFile(list_path ~ LocalPackagesFilename); + enforce(packlist.type == Json.Type.Array, LocalPackagesFilename~" must contain an array."); + foreach( pentry; packlist ){ + try { + auto name = pentry.name.get!string(); + auto ver = pentry["version"].get!string(); + auto path = Path(pentry.path.get!string()); + auto info = Json.EmptyObject; + if( existsFile(path ~ PackageJsonFilename) ) info = jsonFromFile(path ~ PackageJsonFilename); + if( "name" in info && info.name.get!string() != name ) + logWarn("Local package at %s has different name than %s (%s)", path.toNativeString(), name, info.name.get!string()); + info.name = name; + info["version"] = ver; + auto pp = new Package(info, InstallLocation.Local, path); + packs ~= pp; + } catch( Exception e ){ + logWarn("Error adding local package: %s", e.msg); + } + } + } catch( Exception e ){ + logDebug("Loading of local package list at %s failed: %s", list_path.toNativeString(), e.msg); + } + } + scanLocalPackages(m_systemPackagePath, m_localSystemPackages); + scanLocalPackages(m_userPackagePath, m_localUserPackages); + } + + private Package[]* getLocalPackageList(LocalPackageType type) + { + final switch(type){ + case LocalPackageType.user: return &m_localUserPackages; + case LocalPackageType.system: return &m_localSystemPackages; + case LocalPackageType.temporary: return &m_localTemporaryPackages; + } + } + + private void writeLocalPackageList(LocalPackageType type) + { + Package[]* packs = getLocalPackageList(type); + Json[] newlist; + foreach( p; *packs ){ + auto entry = Json.EmptyObject; + entry["name"] = p.name; + entry["version"] = p.ver.toString(); + entry["path"] = p.path.toNativeString(); + newlist ~= entry; + } + + Path path; + final switch(type){ + case LocalPackageType.user: path = m_userPackagePath; break; + case LocalPackageType.system: path = m_systemPackagePath; break; + case LocalPackageType.temporary: return; + } + if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString()); + writeJsonFile(path ~ LocalPackagesFilename, Json(newlist)); + } +} \ No newline at end of file diff --git a/source/dub/platform.d b/source/dub/platform.d new file mode 100644 index 0000000..67f56e5 --- /dev/null +++ b/source/dub/platform.d @@ -0,0 +1,88 @@ +/** + Determines the strings to identify the current build platform. + + Copyright: © 2012 rejectedsoftware e.K. + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Sönke Ludwig +*/ +module dub.platform; + +import std.array; + +string[] determinePlatform() +{ + auto ret = appender!(string[])(); + version(Windows) ret.put("windows"); + version(linux) ret.put("linux"); + version(Posix) ret.put("posix"); + version(OSX) ret.put("osx"); + version(FreeBSD) ret.put("freebsd"); + version(OpenBSD) ret.put("openbsd"); + version(NetBSD) ret.put("netbsd"); + version(DragonFlyBSD) ret.put("dragonflybsd"); + version(BSD) ret.put("bsd"); + version(Solaris) ret.put("solaris"); + version(AIX) ret.put("aix"); + version(Haiku) ret.put("haiku"); + version(SkyOS) ret.put("skyos"); + version(SysV3) ret.put("sysv3"); + version(SysV4) ret.put("sysv4"); + version(Hurd) ret.put("hurd"); + version(Android) ret.put("android"); + version(Cygwin) ret.put("cygwin"); + version(MinGW) ret.put("mingw"); + return ret.data; +} + +string[] determineArchitecture() +{ + auto ret = appender!(string[])(); + version(X86) ret.put("x86"); + version(X86_64) ret.put("x86_64"); + version(ARM) ret.put("arm"); + version(ARM_Thumb) ret.put("arm_thumb"); + version(ARM_Soft) ret.put("arm_soft"); + version(ARM_SoftFP) ret.put("arm_softfp"); + version(ARM_HardFP) ret.put("arm_hardfp"); + version(ARM64) ret.put("arm64"); + version(PPC) ret.put("ppc"); + version(PPC_SoftFP) ret.put("ppc_softfp"); + version(PPC_HardFP) ret.put("ppc_hardfp"); + version(PPC64) ret.put("ppc64"); + version(IA64) ret.put("ia64"); + version(MIPS) ret.put("mips"); + version(MIPS32) ret.put("mips32"); + version(MIPS64) ret.put("mips64"); + version(MIPS_O32) ret.put("mips_o32"); + version(MIPS_N32) ret.put("mips_n32"); + version(MIPS_O64) ret.put("mips_o64"); + version(MIPS_N64) ret.put("mips_n64"); + version(MIPS_EABI) ret.put("mips_eabi"); + version(MIPS_NoFloat) ret.put("mips_nofloat"); + version(MIPS_SoftFloat) ret.put("mips_softfloat"); + version(MIPS_HardFloat) ret.put("mips_hardfloat"); + version(SPARC) ret.put("sparc"); + version(SPARC_V8Plus) ret.put("sparc_v8plus"); + version(SPARC_SoftFP) ret.put("sparc_softfp"); + version(SPARC_HardFP) ret.put("sparc_hardfp"); + version(SPARC64) ret.put("sparc64"); + version(S390) ret.put("s390"); + version(S390X) ret.put("s390x"); + version(HPPA) ret.put("hppa"); + version(HPPA64) ret.put("hppa64"); + version(SH) ret.put("sh"); + version(SH64) ret.put("sh64"); + version(Alpha) ret.put("alpha"); + version(Alpha_SoftFP) ret.put("alpha_softfp"); + version(Alpha_HardFP) ret.put("alpha_hardfp"); + return ret.data; +} + +string determineCompiler() +{ + version(DigitalMars) return "dmd"; + else version(GNU) return "gdc"; + else version(LDC) return "ldc"; + else version(SDC) return "sdc"; + else return null; +} diff --git a/source/dub/registry.d b/source/dub/registry.d index 14f1302..0b47578 100644 --- a/source/dub/registry.d +++ b/source/dub/registry.d Binary files differ diff --git a/source/dub/utils.d b/source/dub/utils.d index 7cbab7e..d890bf4 100644 --- a/source/dub/utils.d +++ b/source/dub/utils.d @@ -30,7 +30,8 @@ return true; } -package Json jsonFromFile(Path file) { +package Json jsonFromFile(Path file, bool silent_fail = false) { + if( silent_fail && !existsFile(file) ) return Json.EmptyObject; auto f = openFile(to!string(file), FileMode.Read); scope(exit) f.close(); auto text = stripUTF8Bom(cast(string)f.readAll()); @@ -47,7 +48,20 @@ return parseJson(text); } +package void writeJsonFile(Path path, Json json) +{ + auto f = openFile(path, FileMode.CreateTrunc); + scope(exit) f.close(); + toPrettyJson(f, json); +} + package bool isPathFromZip(string p) { enforce(p.length > 0); return p[$-1] == '/'; } + +package bool existsDirectory(Path path) { + if( !existsFile(path) ) return false; + auto fi = getFileInfo(path); + return fi.isDirectory; +} \ No newline at end of file