diff --git a/source/app.d b/source/app.d
index b159601..ab63f90 100644
--- a/source/app.d
+++ b/source/app.d
@@ -9,8 +9,9 @@
import dub.dependency;
import dub.dub;
-import dub.platform;
import dub.package_;
+import dub.platform;
+import dub.project;
import dub.registry;
import vibe.core.file;
diff --git a/source/dub/dub.d b/source/dub/dub.d
index b2d8859..e18d329 100644
--- a/source/dub/dub.d
+++ b/source/dub/dub.d
@@ -14,6 +14,7 @@
import dub.package_;
import dub.packagemanager;
import dub.packagesupplier;
+import dub.project;
import dub.generators.generator;
import vibe.core.file;
@@ -34,429 +35,6 @@
import stdx.process;
-/// Actions to be performed by the dub
-private struct Action {
- enum ActionId {
- InstallUpdate,
- Uninstall,
- Conflict,
- Failure
- }
-
- immutable {
- ActionId action;
- string packageId;
- Dependency vers;
- }
- const Package pack;
- const Dependency[string] issuer;
-
- this(ActionId id, string pkg, in Dependency d, Dependency[string] issue)
- {
- action = id;
- packageId = pkg;
- vers = new immutable(Dependency)(d);
- issuer = issue;
- }
-
- this(ActionId id, Package pkg, Dependency[string] issue)
- {
- pack = pkg;
- action = id;
- packageId = pkg.name;
- vers = new immutable(Dependency)("==", pkg.vers);
- issuer = issue;
- }
-
- string toString() const {
- return to!string(action) ~ ": " ~ packageId ~ ", " ~ to!string(vers);
- }
-}
-
-/// During check to build task list, which can then be executed.
-private class Application {
- private {
- Path m_root;
- PackageManager m_packageManager;
- Json m_json;
- Package m_main;
- //Package[string] m_packages;
- Package[] m_dependencies;
- }
-
- this(PackageManager package_manager, Path project_path)
- {
- m_root = project_path;
- m_packageManager = package_manager;
- m_json = Json.EmptyObject;
- reinit();
- }
-
- @property Path binaryPath() const { auto p = m_main.binaryPath; return p.length ? Path(p) : Path("./"); }
-
- string getDefaultConfiguration(BuildPlatform platform)
- const {
- string ret;
- foreach( p; m_dependencies ){
- auto c = p.getDefaultConfiguration(platform);
- if( c.length ) ret = c;
- }
- auto c = m_main.getDefaultConfiguration(platform);
- if( c ) ret = c;
- return ret;
- }
-
- /// Gathers information
- string info() const {
- if(!m_main)
- return "-Unregocgnized application in '"~to!string(m_root)~"' (properly no package.json in this directory)";
- string s = "-Application identifier: " ~ m_main.name;
- s ~= "\n" ~ m_main.info();
- s ~= "\n-Installed dependencies:";
- foreach(p; m_dependencies)
- s ~= "\n" ~ p.info();
- return s;
- }
-
- /// Gets all installed packages as a "packageId" = "version" associative array
- string[string] installedPackagesIDs() const {
- string[string] pkgs;
- foreach(p; m_dependencies)
- pkgs[p.name] = p.vers;
- return pkgs;
- }
-
- const(Package[]) installedPackages() const {
- return m_dependencies;
- }
-
- const (Package) mainPackage() const {
- return m_main;
- }
-
- /// Writes the application's metadata to the package.json file
- /// in it's root folder.
- void writeMetadata() const {
- assert(false);
- // TODO
- }
-
- /// Rereads the applications state.
- void reinit() {
- m_dependencies = null;
- m_main = null;
- m_packageManager.refresh();
-
- try m_json = jsonFromFile(m_root ~ ".dub/dub.json", true);
- catch(Exception t) logDebug("Failed to read .dub/dub.json: %s", t.msg);
-
- if( !existsFile(m_root~PackageJsonFilename) ){
- logWarn("There was no '"~PackageJsonFilename~"' found for the application in '%s'.", m_root.toNativeString());
- return;
- }
-
- m_main = new Package(InstallLocation.Local, m_root);
-
- // TODO: compute the set of mutual dependencies first
- // (i.e. ">=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"; }
-
- @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;
- }
-
- /// 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[] determineActions(PackageSupplier packageSupplier, int option) {
- scope(exit) writeDubJson();
-
- if(!m_main) {
- Action[] a;
- return a;
- }
-
- auto graph = new DependencyGraph(m_main);
- if(!gatherMissingDependencies(packageSupplier, graph) || graph.missing().length > 0) {
- logError("The dependency graph could not be filled.");
- Action[] actions;
- foreach( string pkg, rdp; graph.missing())
- actions ~= Action(Action.ActionId.Failure, pkg, rdp.dependency, rdp.packages);
- return actions;
- }
-
- auto conflicts = graph.conflicted();
- if(conflicts.length > 0) {
- logDebug("Conflicts found");
- Action[] actions;
- foreach( string pkg, dbp; conflicts)
- actions ~= Action(Action.ActionId.Conflict, pkg, dbp.dependency, dbp.packages);
- return actions;
- }
-
- // Gather installed
- Package[string] installed;
- installed[m_main.name] = m_main;
- 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);
-
- // Check against installed and add install actions
- Action[] actions;
- Action[] uninstalls;
- foreach( string pkg, d; graph.needed() ) {
- auto p = pkg in installed;
- // TODO: auto update to latest head revision
- if(!p || (!d.dependency.matches(p.vers) && !d.dependency.matches(Version.MASTER))) {
- if(!p) logDebug("Application not complete, required package '"~pkg~"', which was not found.");
- else logDebug("Application not complete, required package '"~pkg~"', invalid version. Required '%s', available '%s'.", d.dependency, p.vers);
- actions ~= Action(Action.ActionId.InstallUpdate, pkg, d.dependency, d.packages);
- } else {
- logDebug("Required package '"~pkg~"' found with version '"~p.vers~"'");
- if( option & UpdateOptions.Reinstall ) {
- 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 )
- unused.remove(pkg);
- }
- }
-
- // Add uninstall actions
- 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);
- }
-
- // Ugly "uninstall" comes first
- actions = uninstalls ~ actions;
-
- return actions;
- }
-
- void createZip(string destination) {
- assert(false); // not properly implemented
- /*
- string[] ignores;
- auto ignoreFile = to!string(m_root~"dub.ignore.txt");
- if(exists(ignoreFile)){
- auto iFile = openFile(ignoreFile);
- scope(exit) iFile.close();
- while(!iFile.empty)
- ignores ~= to!string(cast(char[])iFile.readLine());
- logDebug("Using '%s' found by the application.", ignoreFile);
- }
- else {
- ignores ~= ".svn/*";
- ignores ~= ".git/*";
- ignores ~= ".hg/*";
- logDebug("The '%s' file was not found, defaulting to ignore:", ignoreFile);
- }
- ignores ~= ".dub/*"; // .dub will not be included
- foreach(string i; ignores)
- logDebug(" " ~ i);
-
- logDebug("Creating zip file from application: " ~ m_main.name);
- auto archive = new ZipArchive();
- foreach( string file; dirEntries(to!string(m_root), SpanMode.depth) ) {
- enforce( Path(file).startsWith(m_root) );
- auto p = Path(file);
- p = p[m_root.length..p.length];
- if(isDir(file)) continue;
- foreach(string ignore; ignores)
- if(globMatch(file, ignore))
- would work, as I see it;
- continue;
- logDebug(" Adding member: %s", p);
- ArchiveMember am = new ArchiveMember();
- am.name = to!string(p);
- auto f = openFile(file);
- scope(exit) f.close();
- am.expandedData = f.readAll();
- archive.addMember(am);
- }
-
- logDebug(" Writing zip: %s", destination);
- auto dst = openFile(destination, FileMode.CreateTrunc);
- scope(exit) dst.close();
- dst.write(cast(ubyte[])archive.build());
- */
- }
-
- 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");
- return false;
- }
- }
-
- oldMissing = missing.dup;
- logTrace("There are %s packages missing.", missing.length);
- foreach(string pkg, reqDep; missing) {
- if(!reqDep.dependency.valid()) {
- logTrace("Dependency to "~pkg~" is invalid. Trying to fix by modifying others.");
- continue;
- }
-
- // 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( p && p.installLocation != InstallLocation.Local && needsUpToDateCheck(pkg) ){
- logInfo("Triggering update of package %s", pkg);
- p = null;
- }
-
- 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);
- }
- graph.clearUnused();
- missing = graph.missing();
- }
- 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;
- }
-
- 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, "dub");
- create(m_json["dub"], "lastUpdate");
- m_json["dub"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() );
-
- writeDubJson();
- }
-
- private void writeDubJson() {
- // don't bother to write an empty file
- if( m_json.length == 0 ) return;
-
- 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.");
- }
- }
-}
/// The default supplier for packages, which is the registry
/// hosted by vibed.org.
@@ -466,13 +44,6 @@
return new RegistryPS(url);
}
-enum UpdateOptions
-{
- None = 0,
- JustAnnotate = 1<<0,
- Reinstall = 1<<1
-};
-
/// The Dub class helps in getting the applications
/// dependencies up and running. An instance manages one application.
class Dub {
@@ -483,7 +54,7 @@
Path m_userDubPath, m_systemDubPath;
Json m_systemConfig, m_userConfig;
PackageManager m_packageManager;
- Application m_app;
+ Project m_app;
}
/// Initiales the package manager for the vibe application
@@ -525,7 +96,7 @@
{
m_root = m_cwd;
m_packageManager.projectPackagePath = m_root ~ ".dub/packages/";
- m_app = new Application(m_packageManager, m_root);
+ m_app = new Project(m_packageManager, m_root);
}
/// Returns a list of flags which the application needs to be compiled
@@ -663,66 +234,3 @@
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, '$');
- }
- vres.put(var);
- var = vres.data;
- }
- 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;
- }
- dst.put(p.toNativeString());
- } else dst.put(var);
- }
-}
-
-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/generators/generator.d b/source/dub/generators/generator.d
index 94a0547..430c1c1 100644
--- a/source/dub/generators/generator.d
+++ b/source/dub/generators/generator.d
@@ -7,8 +7,9 @@
*/
module dub.generators.generator;
-import dub.dub;
+import dub.project;
import dub.packagemanager;
+import dub.generators.monod;
import dub.generators.visuald;
import vibe.core.log;
import std.exception;
@@ -20,11 +21,14 @@
}
/// Creates a project generator.
-ProjectGenerator createProjectGenerator(string projectType, Application app, PackageManager mgr) {
+ProjectGenerator createProjectGenerator(string projectType, Project app, PackageManager mgr) {
enforce(app !is null, "app==null, Need an application to work on!");
enforce(mgr !is null, "mgr==null, Need a package manager to work on!");
switch(projectType) {
default: return null;
+ case "MonoD":
+ logTrace("Generating MonoD generator.");
+ return new MonoDGenerator(app, mgr);
case "VisualD":
logTrace("Generating VisualD generator.");
return new VisualDGenerator(app, mgr);
diff --git a/source/dub/generators/monod.d b/source/dub/generators/monod.d
index e69de29..3384a8f 100644
--- a/source/dub/generators/monod.d
+++ b/source/dub/generators/monod.d
@@ -0,0 +1,212 @@
+/**
+ Generator for MonoD project files
+
+ Copyright: © 2013 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.generators.monod;
+
+import std.algorithm;
+import std.array;
+import std.conv;
+import std.format;
+import std.uuid;
+import std.exception;
+
+import vibe.core.file;
+import vibe.core.log;
+
+import dub.project;
+import dub.package_;
+import dub.packagemanager;
+import dub.generators.generator;
+
+class MonoDGenerator : ProjectGenerator {
+ private {
+ Project m_app;
+ PackageManager m_pkgMgr;
+ string[string] m_projectUuids;
+ bool m_singleProject = true;
+ }
+
+ this(Project app, PackageManager mgr)
+ {
+ m_app = app;
+ m_pkgMgr = mgr;
+ }
+
+ void generateProject()
+ {
+ logTrace("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, to!string(m_app.mainPackage().dependencies().length));
+ /+generateProjects(m_app.mainPackage());
+ generateSolution();+/
+ }
+
+ /+private void generateSolution()
+ {
+ auto sln = openFile(m_app.mainPackage().name ~ ".sln", FileMode.CreateTrunc);
+ scope(exit) sln.close();
+
+ // Writing solution file
+ logTrace("About to write to .sln file.");
+
+ // Solution header
+ sln.put('\n');
+ sln.put("Microsoft Visual Studio Solution File, Format Version 11.00\n");
+ sln.put("# Visual Studio 2010\n");
+
+ generateSolutionEntry(sln, main);
+ if( m_singleProject ) enforce(main == m_app.mainPackage());
+ else performOnDependencies(main, (const Package pack) { generateSolutionEntries(sln, pack); } );
+
+ sln.put("Global\n");
+
+ // configuration platforms
+ sln.put("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n");
+ foreach(config; allconfigs)
+ sln.formattedWrite("\t\t%s|%s = %s|%s\n", config.configName, config.plaformName);
+ sln.put("\tEndGlobalSection\n");
+
+ // configuration platforms per project
+ sln.put("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n");
+ generateSolutionConfig(sln, m_app.mainPackage());
+ auto projectUuid = guid(pack.name());
+ foreach(config; allconfigs)
+ foreach(s; ["ActiveCfg", "Build.0"])
+ sln.formattedWrite("\n\t\t%s.%s|%s.%s = %s|%s",
+ projectUuid, config.configName, config.platformName, s,
+ config.configName, config.platformName);
+ // TODO: for all dependencies
+ sln.put("\tEndGlobalSection\n");
+
+ // solution properties
+ sln.put("\tGlobalSection(SolutionProperties) = preSolution\n");
+ sln.put("\t\tHideSolutionNode = FALSE\n");
+ sln.put("\tEndGlobalSection\n");
+
+ // monodevelop properties
+ sln.put("\tGlobalSection(MonoDevelopProperties) = preSolution\n");
+ sln.formattedWrite("\t\tStartupItem = %s\n", "monodtest/monodtest.dproj");
+ sln.put("\tEndGlobalSection\n");
+
+ sln.put("EndGlobal\n");
+ }
+
+ private void generateSolutionEntry(OutputStream ret, const Package pack)
+ {
+ auto projUuid = generateUUID();
+ auto projName = pack.name;
+ auto projPath = pack.name ~ ".visualdproj";
+ auto projectUuid = guid(projName);
+
+ // Write project header, like so
+ // Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "derelict", "..\inbase\source\derelict.visualdproj", "{905EF5DA-649E-45F9-9C15-6630AA815ACB}"
+ ret.formattedWrite("\nProject(\"%s\") = \"%s\", \"%s\", \"%s\"",
+ projUuid, projName, projPath, projectUuid);
+
+ if( !m_singleProject ){
+ if(pack.dependencies.length > 0) {
+ ret.formattedWrite("
+ ProjectSection(ProjectDependencies) = postProject");
+ foreach(id, dependency; pack.dependencies) {
+ // TODO: clarify what "uuid = uuid" should mean
+ auto uuid = guid(id);
+ ret.formattedWrite("
+ %s = %s", uuid, uuid);
+ }
+ ret.formattedWrite("
+ EndProjectSection");
+ }
+ }
+
+ ret.formattedWrite("\nEndProject");
+ }
+
+ private void generateProjects(in Package pack)
+ {
+ bool[const(Package)] visited;
+
+ void generateRec(in Package p){
+ if( p in visited ) return;
+ visited[p] = true;
+
+ generateProject(p);
+
+ if( !m_singleProject )
+ performOnDependencies(p, &generateRec);
+ }
+ }
+
+ private void generateProject(in Package pack) {
+ logTrace("About to write to '%s.dproj' file", pack.name);
+ auto sln = openFile(pack.name ~ ".dproj", FileMode.CreateTrunc);
+ scope(exit) sln.close();
+
+ sln.put("\n");
+ sln.put("\n");
+ // TODO: property groups
+
+ auto projName = pack.name;
+
+ void generateProperties(Configuration config)
+ {
+ sln.formattedWrite("\t Condition=\" '$(Configuration)|$(Platform)' == '%s|%s' \"\n",
+ config.configName, config.platformName);
+
+ // TODO!
+ sln.put("\n\n");
+ }
+
+ foreach(config; allconfigs)
+ generateProperties(config);
+
+
+ bool[const(Package)] visited;
+ void generateSources(in Package p)
+ {
+ if( p in visited ) return;
+ visited[p] = true;
+
+ foreach( s; p.sources )
+ sln.formattedWrite("\t\t\n", s);
+
+ if( m_singleProject ){
+ foreach( dep; p.dependencies )
+ generateSources(dep);
+ }
+ }
+
+
+ sln.put("\t\n");
+ generateSources(pack);
+ sln.put("\t\n");
+ sln.put("");
+ }
+
+ void performOnDependencies(const Package main, void delegate(const Package pack) op)
+ {
+ foreach(id, dependency; main.dependencies){
+ logDebug("Retrieving package %s from package manager.", id);
+ auto pack = m_pkgMgr.getBestPackage(id, dependency);
+ if(pack is null) {
+ logWarn("Package %s (%s) could not be retrieved continuing...", id, to!string(dependency));
+ continue;
+ }
+ logDebug("Performing on retrieved package %s", pack.name);
+ op(pack);
+ }
+ }
+
+ string generateUUID()
+ const {
+ return "{" ~ randomUUID().toString() ~ "}";
+ }
+
+ string guid(string projectName)
+ {
+ if(projectName !in m_projectUuids)
+ m_projectUuids[projectName] = generateUUID();
+ return m_projectUuids[projectName];
+ }+/
+}
\ No newline at end of file
diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d
index 89b0e89..9db90c7 100644
--- a/source/dub/generators/visuald.d
+++ b/source/dub/generators/visuald.d
@@ -17,7 +17,7 @@
import vibe.core.file;
import vibe.core.log;
-import dub.dub;
+import dub.project;
import dub.package_;
import dub.packagemanager;
import dub.generators.generator;
@@ -27,13 +27,13 @@
class VisualDGenerator : ProjectGenerator {
private {
- Application m_app;
+ Project m_app;
PackageManager m_pkgMgr;
string[string] m_projectUuids;
bool[string] m_generatedProjects;
}
- this(Application app, PackageManager mgr) {
+ this(Project app, PackageManager mgr) {
m_app = app;
m_pkgMgr = mgr;
}
@@ -238,43 +238,14 @@
}
void generateProjectConfiguration(Appender!(char[]) ret, const Package pack, Config type) {
+ BuildPlatform platform;
+ platform.platform ~= "windows";
+ platform.architecture ~= "x86";
+ platform.compiler = "dmd";
- // Helper functions used within.
- string[] getSettingsFromBuildSettings(BuildSettings bs, in string setting) {
- // TODO: make nice, compile time string stuff?
- switch(setting) {
- case "dflags": return bs.dflags;
- case "lflags": return bs.lflags;
- case "libs": return bs.libs;
- case "files": return bs.files;
- case "copyFiles": return bs.copyFiles;
- case "versions": return bs.versions;
- case "importPaths": return bs.importPaths;
- case "stringImportPaths": return bs.stringImportPaths;
- default: assert(false);
- }
- }
- string[] getSettings(in Package pack, in string setting, bool prefixPath) {
- BuildPlatform platform;
- platform.platform ~= "windows";
- platform.architecture ~= "x86";
- platform.compiler = "dmd";
- version(VISUALD_SEPERATE_PROJECT_FILES) {
- assert(false, "Not implemented");
- }
- version(VISUALD_SINGLE_PROJECT_FILE) {
- string[] ret;
- performOnDependencies(pack, (const Package dep) { ret ~= getSettings(dep, setting, prefixPath); } );
- if(prefixPath) {
- string[] itms = getSettingsFromBuildSettings(pack.getBuildSettings(platform, ""), setting);
- foreach(i; itms)
- ret ~= to!string(pack.path) ~ "\\" ~ i;
- }
- else
- ret ~= getSettingsFromBuildSettings(pack.getBuildSettings(platform, ""), setting);
- return ret;
- }
- }
+ auto settings = m_app.getBuildSettings(platform, m_app.getDefaultConfiguration(platform));
+
+ string[] getSettings(string setting)(){ return __traits(getMember, settings, setting); }
// Specify build configuration name
ret.formattedWrite("
@@ -360,7 +331,7 @@
"); // version ids ?
// Add version identifiers
- string versions = join(getSettings(pack, "versions", false), " ");
+ string versions = join(getSettings!"versions"(), " ");
ret.formattedWrite("
%s", versions);
@@ -386,8 +357,8 @@
");
// Add libraries.
- string linkLibs = join(getSettings(pack, "libs", false), " ");
- string addLinkFiles = join(getSettings(pack, "files", true), " ");
+ string linkLibs = join(getSettings!"libs"(), " ");
+ string addLinkFiles = join(getSettings!"files"(), " ");
ret.formattedWrite("
%s", linkLibs ~ " " ~ addLinkFiles);
diff --git a/source/dub/project.d b/source/dub/project.d
new file mode 100644
index 0000000..2a880d8
--- /dev/null
+++ b/source/dub/project.d
@@ -0,0 +1,530 @@
+/**
+ A package manager.
+
+ Copyright: © 2012 Matthias Dondorff
+ License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
+ Authors: Matthias Dondorff, Sönke Ludwig
+*/
+module dub.project;
+
+import dub.dependency;
+import dub.installation;
+import dub.utils;
+import dub.registry;
+import dub.package_;
+import dub.packagemanager;
+import dub.packagesupplier;
+import dub.generators.generator;
+
+import vibe.core.file;
+import vibe.core.log;
+import vibe.data.json;
+import vibe.inet.url;
+
+// todo: cleanup imports.
+import std.algorithm;
+import std.array;
+import std.conv;
+import std.datetime;
+import std.exception;
+import std.file;
+import std.string;
+import std.typecons;
+import std.zip;
+import stdx.process;
+
+
+/// During check to build task list, which can then be executed.
+class Project {
+ private {
+ Path m_root;
+ PackageManager m_packageManager;
+ Json m_json;
+ Package m_main;
+ //Package[string] m_packages;
+ Package[] m_dependencies;
+ }
+
+ this(PackageManager package_manager, Path project_path)
+ {
+ m_root = project_path;
+ m_packageManager = package_manager;
+ m_json = Json.EmptyObject;
+ reinit();
+ }
+
+ @property Path binaryPath() const { auto p = m_main.binaryPath; return p.length ? Path(p) : Path("./"); }
+
+ string getDefaultConfiguration(BuildPlatform platform)
+ const {
+ string ret;
+ foreach( p; m_dependencies ){
+ auto c = p.getDefaultConfiguration(platform);
+ if( c.length ) ret = c;
+ }
+ auto c = m_main.getDefaultConfiguration(platform);
+ if( c ) ret = c;
+ return ret;
+ }
+
+ /// Gathers information
+ string info() const {
+ if(!m_main)
+ return "-Unregocgnized application in '"~to!string(m_root)~"' (properly no package.json in this directory)";
+ string s = "-Application identifier: " ~ m_main.name;
+ s ~= "\n" ~ m_main.info();
+ s ~= "\n-Installed dependencies:";
+ foreach(p; m_dependencies)
+ s ~= "\n" ~ p.info();
+ return s;
+ }
+
+ /// Gets all installed packages as a "packageId" = "version" associative array
+ string[string] installedPackagesIDs() const {
+ string[string] pkgs;
+ foreach(p; m_dependencies)
+ pkgs[p.name] = p.vers;
+ return pkgs;
+ }
+
+ const(Package[]) installedPackages() const {
+ return m_dependencies;
+ }
+
+ const (Package) mainPackage() const {
+ return m_main;
+ }
+
+ /// Writes the application's metadata to the package.json file
+ /// in it's root folder.
+ void writeMetadata() const {
+ assert(false);
+ // TODO
+ }
+
+ /// Rereads the applications state.
+ void reinit() {
+ m_dependencies = null;
+ m_main = null;
+ m_packageManager.refresh();
+
+ try m_json = jsonFromFile(m_root ~ ".dub/dub.json", true);
+ catch(Exception t) logDebug("Failed to read .dub/dub.json: %s", t.msg);
+
+ if( !existsFile(m_root~PackageJsonFilename) ){
+ logWarn("There was no '"~PackageJsonFilename~"' found for the application in '%s'.", m_root.toNativeString());
+ return;
+ }
+
+ m_main = new Package(InstallLocation.Local, m_root);
+
+ // TODO: compute the set of mutual dependencies first
+ // (i.e. ">=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"; }
+
+ @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;
+ }
+
+ /// 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[] determineActions(PackageSupplier packageSupplier, int option) {
+ scope(exit) writeDubJson();
+
+ if(!m_main) {
+ Action[] a;
+ return a;
+ }
+
+ auto graph = new DependencyGraph(m_main);
+ if(!gatherMissingDependencies(packageSupplier, graph) || graph.missing().length > 0) {
+ logError("The dependency graph could not be filled.");
+ Action[] actions;
+ foreach( string pkg, rdp; graph.missing())
+ actions ~= Action(Action.ActionId.Failure, pkg, rdp.dependency, rdp.packages);
+ return actions;
+ }
+
+ auto conflicts = graph.conflicted();
+ if(conflicts.length > 0) {
+ logDebug("Conflicts found");
+ Action[] actions;
+ foreach( string pkg, dbp; conflicts)
+ actions ~= Action(Action.ActionId.Conflict, pkg, dbp.dependency, dbp.packages);
+ return actions;
+ }
+
+ // Gather installed
+ Package[string] installed;
+ installed[m_main.name] = m_main;
+ 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);
+
+ // Check against installed and add install actions
+ Action[] actions;
+ Action[] uninstalls;
+ foreach( string pkg, d; graph.needed() ) {
+ auto p = pkg in installed;
+ // TODO: auto update to latest head revision
+ if(!p || (!d.dependency.matches(p.vers) && !d.dependency.matches(Version.MASTER))) {
+ if(!p) logDebug("Application not complete, required package '"~pkg~"', which was not found.");
+ else logDebug("Application not complete, required package '"~pkg~"', invalid version. Required '%s', available '%s'.", d.dependency, p.vers);
+ actions ~= Action(Action.ActionId.InstallUpdate, pkg, d.dependency, d.packages);
+ } else {
+ logDebug("Required package '"~pkg~"' found with version '"~p.vers~"'");
+ if( option & UpdateOptions.Reinstall ) {
+ 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 )
+ unused.remove(pkg);
+ }
+ }
+
+ // Add uninstall actions
+ 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);
+ }
+
+ // Ugly "uninstall" comes first
+ actions = uninstalls ~ actions;
+
+ return actions;
+ }
+
+ void createZip(string destination) {
+ assert(false); // not properly implemented
+ /*
+ string[] ignores;
+ auto ignoreFile = to!string(m_root~"dub.ignore.txt");
+ if(exists(ignoreFile)){
+ auto iFile = openFile(ignoreFile);
+ scope(exit) iFile.close();
+ while(!iFile.empty)
+ ignores ~= to!string(cast(char[])iFile.readLine());
+ logDebug("Using '%s' found by the application.", ignoreFile);
+ }
+ else {
+ ignores ~= ".svn/*";
+ ignores ~= ".git/*";
+ ignores ~= ".hg/*";
+ logDebug("The '%s' file was not found, defaulting to ignore:", ignoreFile);
+ }
+ ignores ~= ".dub/*"; // .dub will not be included
+ foreach(string i; ignores)
+ logDebug(" " ~ i);
+
+ logDebug("Creating zip file from application: " ~ m_main.name);
+ auto archive = new ZipArchive();
+ foreach( string file; dirEntries(to!string(m_root), SpanMode.depth) ) {
+ enforce( Path(file).startsWith(m_root) );
+ auto p = Path(file);
+ p = p[m_root.length..p.length];
+ if(isDir(file)) continue;
+ foreach(string ignore; ignores)
+ if(globMatch(file, ignore))
+ would work, as I see it;
+ continue;
+ logDebug(" Adding member: %s", p);
+ ArchiveMember am = new ArchiveMember();
+ am.name = to!string(p);
+ auto f = openFile(file);
+ scope(exit) f.close();
+ am.expandedData = f.readAll();
+ archive.addMember(am);
+ }
+
+ logDebug(" Writing zip: %s", destination);
+ auto dst = openFile(destination, FileMode.CreateTrunc);
+ scope(exit) dst.close();
+ dst.write(cast(ubyte[])archive.build());
+ */
+ }
+
+ 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");
+ return false;
+ }
+ }
+
+ oldMissing = missing.dup;
+ logTrace("There are %s packages missing.", missing.length);
+ foreach(string pkg, reqDep; missing) {
+ if(!reqDep.dependency.valid()) {
+ logTrace("Dependency to "~pkg~" is invalid. Trying to fix by modifying others.");
+ continue;
+ }
+
+ // 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( p && p.installLocation != InstallLocation.Local && needsUpToDateCheck(pkg) ){
+ logInfo("Triggering update of package %s", pkg);
+ p = null;
+ }
+
+ 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);
+ }
+ graph.clearUnused();
+ missing = graph.missing();
+ }
+ 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;
+ }
+
+ 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, "dub");
+ create(m_json["dub"], "lastUpdate");
+ m_json["dub"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() );
+
+ writeDubJson();
+ }
+
+ private void writeDubJson() {
+ // don't bother to write an empty file
+ if( m_json.length == 0 ) return;
+
+ 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.");
+ }
+ }
+}
+
+/// Actions to be performed by the dub
+struct Action {
+ enum ActionId {
+ InstallUpdate,
+ Uninstall,
+ Conflict,
+ Failure
+ }
+
+ immutable {
+ ActionId action;
+ string packageId;
+ Dependency vers;
+ }
+ const Package pack;
+ const Dependency[string] issuer;
+
+ this(ActionId id, string pkg, in Dependency d, Dependency[string] issue)
+ {
+ action = id;
+ packageId = pkg;
+ vers = new immutable(Dependency)(d);
+ issuer = issue;
+ }
+
+ this(ActionId id, Package pkg, Dependency[string] issue)
+ {
+ pack = pkg;
+ action = id;
+ packageId = pkg.name;
+ vers = new immutable(Dependency)("==", pkg.vers);
+ issuer = issue;
+ }
+
+ string toString() const {
+ return to!string(action) ~ ": " ~ packageId ~ ", " ~ to!string(vers);
+ }
+}
+
+enum UpdateOptions
+{
+ None = 0,
+ JustAnnotate = 1<<0,
+ Reinstall = 1<<1
+};
+
+
+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, '$');
+ }
+ vres.put(var);
+ var = vres.data;
+ }
+ 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;
+ }
+ dst.put(p.toNativeString());
+ } else dst.put(var);
+ }
+}
+
+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