diff --git a/package.json b/package.json index 4250ef0..175de5a 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,12 @@ "name": "dub", "description": "Package manager for D packages", "copyright": "Copyright 2012 rejectedsoftware e.K.", + "version": "0.0.1", "authors": [ "Matthias Dondorff", "Sönke Ludwig" - ] + ], + "dependencies": { + "vibe.d" : ">=0.0.0" + } } \ No newline at end of file diff --git a/source/app.d b/source/app.d index 36d35c7..6f72d39 100644 --- a/source/app.d +++ b/source/app.d @@ -57,7 +57,6 @@ 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]); @@ -148,6 +147,19 @@ logDebug("vpm initialized"); vpm.update(UpdateOptions.Reinstall | (annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None)); break; + case "generate": + string ide; + if( args.length >= 2 ) ide = args[1]; + if(ide.empty) { + logInfo("Usage: dub generate "); + return -1; + } + + Vpm vpm = new Vpm(Path(appPath), new RegistryPS(registryUrl)); + logDebug("vpm initialized, calling generate"); + vpm.generateProject(ide); + logDebug("Project files generated."); + return 0; } auto script = openFile(to!string(dstScript), FileMode.CreateTrunc); @@ -181,6 +193,7 @@ run Compiles and runs the application build Just compiles the application in the project directory upgrade Forces an upgrade of all dependencies + generate Generates project files for a specified IDE. Options: -v --verbose Also output debug messages @@ -260,7 +273,19 @@ //Otherwise use the current directory. else cwd = Path("."); - + + //Make sure we do not overwrite anything accidentally + if( (existsFile(cwd ~ "package.json")) || + (existsFile(cwd ~ "source" )) || + (existsFile(cwd ~ "views" )) || + (existsFile(cwd ~ "public" ))) + { + logInfo("The current directory is not empty.\n" + "vibe init aborted."); + //Exit Immediately. + return; + } + //raw strings must be unindented. immutable packageJson = `{ @@ -284,17 +309,7 @@ logInfo("Edit source/app.d to start your project."); } `; - //Make sure we do not overwrite anything accidentally - if( (existsFile(cwd ~ "package.json")) || - (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" ); diff --git a/source/dub/dub.d b/source/dub/dub.d index e5e7dc3..a05fc6d 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -13,6 +13,8 @@ import dub.registry; import dub.package_; import dub.packagesupplier; +import dub.packagestore; +import dub.generators.generator; import vibe.core.file; import vibe.core.log; @@ -81,13 +83,21 @@ } /// Gets all installed packages as a "packageId" = "version" associative array - string[string] installedPackages() const { + string[string] installedPackagesIDs() const { string[string] pkgs; foreach(k, p; m_packages) pkgs[k] = p.vers; return pkgs; } + const(Package[string]) installedPackages() const { + return m_packages; + } + + 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 { @@ -270,108 +280,110 @@ */ } - 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; + 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; + } + } + 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) { - auto o = pkg in oldMissing; - if(o && reqDep.dependency != o.dependency) { - different = true; - break; + 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 = null; + + // 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()); + } + } + + if(p) + graph.insert(p); } - if(!different) { - logWarn("Could not resolve dependencies"); - return false; - } + graph.clearUnused(); + missing = graph.missing(); } - 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 = null; - - // 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()); - } - } - - if(p) - graph.insert(p); - } - graph.clearUnused(); - missing = graph.missing(); - } - - return true; - } - - private 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; } - } - private 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]; + 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; + } } - create(m_json, "vpm"); - create(m_json["vpm"], "lastUpdate"); - m_json["vpm"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() ); - writeVpmJson(); - } + 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 writeVpmJson() { - // don't bother to write an empty file - if( m_json.length == 0 ) return; + writeVpmJson(); + } - 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."); + 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."); + } } } } @@ -392,12 +404,13 @@ }; /// The Vpm or Vibe Package Manager helps in getting the applications -/// dependencies up and running. +/// dependencies up and running. An instance manages one application. class Vpm { private { Path m_root; Application m_app; PackageSupplier m_packageSupplier; + PackageStore m_packageStore; } /// Initiales the package manager for the vibe application @@ -406,7 +419,12 @@ enforce(root.absolute, "Specify an absolute path for the VPM"); m_root = root; m_packageSupplier = ps; + m_packageStore = new PackageStore(); m_app = new Application(root); + + /// HACK + m_packageStore.includePath(Path("E:\\dev\\")); + m_packageStore.includePath(Path("E:\\dev\\dub\\modules")); } /// Returns the name listed in the package.json of the current @@ -483,7 +501,7 @@ } /// Gets all installed packages as a "packageId" = "version" associative array - string[string] installedPackages() const { return m_app.installedPackages(); } + string[string] installedPackages() const { return m_app.installedPackagesIDs(); } /// Installs the package matching the dependency into the application. /// @param addToApplication if true, this will also add an entry in the @@ -628,4 +646,16 @@ rmdir(to!string(packagePath)); logInfo("Uninstalled package: '"~packageId~"'"); } + + /// Generate project files for a specified IDE. + /// Any existing project files will be overridden. + void generateProject(string ide) { + auto generator = createProjectGenerator(ide, m_app, m_packageStore); + if(null is generator) + throw new Exception("Unsupported IDE, there is no generator available for '"~ide~"'"); + + // Q: update before generating? + + generator.generateProject(); + } } diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d new file mode 100644 index 0000000..9c7d251 --- /dev/null +++ b/source/dub/generators/generator.d @@ -0,0 +1,26 @@ +/** + Generator for project files + + Copyright: © 2012 Matthias Dondorff + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Matthias Dondorff +*/ +module dub.generators.generator; + +import dub.dub; +import dub.packagestore; +import dub.generators.visuald; + +/// A project generator generates projects :-/ +interface ProjectGenerator +{ + void generateProject(); +} + +/// Creates a project generator. +ProjectGenerator createProjectGenerator(string projectType, Application app, PackageStore store) { + switch(projectType) { + default: return null; + case "VisualD": return new VisualDGenerator(app, store); + } +} \ No newline at end of file diff --git a/source/dub/generators/ideas.txt b/source/dub/generators/ideas.txt new file mode 100644 index 0000000..80bc414 --- /dev/null +++ b/source/dub/generators/ideas.txt @@ -0,0 +1,12 @@ + + + +Startup project + +packages -> projects + +libraries + +source files + +packages, which are installed globally \ No newline at end of file diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index e69de29..fc510e4 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -0,0 +1,293 @@ +/** + Generator for VisualD project files + + Copyright: © 2012 Matthias Dondorff + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Matthias Dondorff +*/ +module dub.generators.visuald; + +import std.array; +import std.conv; +import std.format; +import std.uuid; + +import vibe.core.file; +import vibe.core.log; + +import dub.dub; +import dub.package_; +import dub.packagestore; +import dub.generators.generator; + +class VisualDGenerator : ProjectGenerator { + private { + Application m_app; + PackageStore m_store; + string[string] m_projectUuids; + bool[string] m_generatedProjects; + } + + this(Application app, PackageStore store) { + m_app = app; + m_store = store; + } + + override void generateProject() { + logDebug("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 { + enum Config { + Release, + Debug, + Unittest + } + + void generateSolution() { + auto ret = appender!(char[])(); + + // Solution header + ret.formattedWrite("Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010"); + + generateSolutionEntries(ret, m_app.mainPackage()); + + // Global section contains configurations + ret.formattedWrite("Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + Unittest|Win32 = Unittest|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution"); + + generateSolutionConfig(ret, m_app.mainPackage()); + + // TODO: for all dependencies + + ret.formattedWrite(" + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal"); + + // Writing solution file + logTrace("About to write to .sln file with %s bytes", to!string(ret.data().length)); + auto sln = openFile(m_app.mainPackage().name ~ ".sln", FileMode.CreateTrunc); + scope(exit) sln.close(); + sln.write(ret.data()); + sln.flush(); + } + + void generateSolutionEntries(Appender!(char[]) ret, const Package main) { + generateSolutionEntry(ret, main); + performOnDependencies(main, (const Package pack) { generateSolutionEntries(ret, pack); } ); + } + + void generateSolutionEntry(Appender!(char[]) 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); + + // TODO: add dependency references + 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"); + } + + void generateSolutionConfig(Appender!(char[]) ret, const Package pack) { + const string[] sub = [ "ActiveCfg", "Build.0" ]; + const string[] conf = [ "Debug|Win32", "Release|Win32", "Unittest|Win32" ]; + auto projectUuid = guid(pack.name()); + foreach(c; conf) + foreach(s; sub) + formattedWrite(ret, "\n\t\t%s.%s.%s = %s", to!string(projectUuid), c, s, c); + } + + void generateProjects(const Package main) { + + // TODO: cyclic check + + generateProj(main); + m_generatedProjects[main.name] = true; + performOnDependencies(main, (const Package dependency) { + if(dependency.name in m_generatedProjects) + return; + generateProjects(dependency); + } ); + } + + void generateProj(const Package pack) { + int i = 0; + auto ret = appender!(char[])(); + + auto projName = pack.name; + ret.formattedWrite( +" + %s", guid(projName)); + + // Several configurations (debug, release, unittest) + generateProjectConfiguration(ret, pack, Config.Release); + generateProjectConfiguration(ret, pack, Config.Debug); + generateProjectConfiguration(ret, pack, Config.Unittest); + + // Add all files + // TODO: nice folders + formattedWrite(ret, " + ", projName); + foreach(source; pack.sources) { + ret.formattedWrite("\n ", source.toString()); + } + ret.formattedWrite(" + +"); + + logTrace("About to write to '%s.visualdproj' file %s bytes", pack.name, to!string(ret.data().length)); + auto sln = openFile(pack.name ~ ".visualdproj", FileMode.CreateTrunc); + scope(exit) sln.close(); + sln.write(ret.data()); + sln.flush(); + } + + void generateProjectConfiguration(Appender!(char[]) ret, const Package pack, Config type) { + ret.formattedWrite( +"\n + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + $(DMDInstallDir)windows\\bin\\dmd.exe + source; ..\\vibe.d\\source + + $(ConfigurationName) + obj\\$(ConfigurationName) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\\$(TargetName).json + 0 + + 0 + DerelictGL_ALL HostWin32 + 0 + 0 + 0 + + + + 0 + + 1 + $(VisualDInstallDir)cv2pdb\\cv2pdb.exe + 0 + 0 + 0 + + + + ws2_32.lib gdi32.lib winmm.lib ..\\vibe.d\\lib\\win-i386\\event2.lib ..\\vibe.d\\lib\\win-i386\\eay.lib ..\\vibe.d\\lib\\win-i386\\ssl.lib + + + + bin\\$(ProjectName)_d.exe + -L/PAGESIZE:1024 + + + *.obj;*.cmd;*.build;*.json;*.dep + ", to!string(type)); + } + + void performOnDependencies(const Package main, void delegate(const Package pack) op) { + // TODO: cyclic check + + foreach(id, dependency; main.dependencies) { + logWarn("Retrieving package %s from store.", id); + logWarn("dudb"); + auto pack = m_store.package_(id, dependency); + if(pack is null) { + logWarn("Package %s (%s) could not be retrieved continuing...", id, to!string(dependency)); + continue; + } + 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/package_.d b/source/dub/package_.d index 2a15078..475dde7 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -12,11 +12,13 @@ import std.array; import std.conv; +import std.file; +import std.exception; + import vibe.core.file; import vibe.data.json; import vibe.inet.url; - /// Representing an installed package // Json file example: // { @@ -39,11 +41,13 @@ private { Json m_meta; Dependency[string] m_dependencies; + Path m_root; } this(Path root) { m_meta = jsonFromFile(root ~ "package.json"); m_dependencies = .dependencies(m_meta); + m_root = root; } this(Json json) { m_meta = json; @@ -62,6 +66,15 @@ return ret.data; } + @property const(Path[]) sources() const { + enforce(m_root != Path(), "Cannot assemble sources from package, m_root == Path()."); + Path[] allSources; + foreach(DirEntry d; dirEntries(to!string(m_root ~ Path("source")), "*.d", SpanMode.depth)) { + allSources ~= Path(d.name); + } + return allSources; + } + string info() const { string s; s ~= cast(string)m_meta["name"] ~ ", version '" ~ cast(string)m_meta["version"] ~ "'"; diff --git a/source/dub/packagestore.d b/source/dub/packagestore.d new file mode 100644 index 0000000..51bc0e6 --- /dev/null +++ b/source/dub/packagestore.d @@ -0,0 +1,38 @@ +/** + A package store, storing and retrieving installed packages. + + Copyright: © 2012 Matthias Dondorff + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Matthias Dondorff +*/ +module dub.packagestore; + +import std.conv; + +import vibe.core.log; +import vibe.inet.path; + +import dub.dependency; +import dub.package_; + +class PackageStore { + + this() { + } + + /// The PackageStore will use this directory to lookup packages. + void includePath(Path path) { m_includePaths ~= path; } + + /// Retrieves an installed package. + Package package_(string packageId, const Dependency dep) { + logDebug("PackageStore.package_('%s', '%s')", packageId, to!string(dep)); + if(packageId == "vibe.d") { + return new Package(Path("E:\\dev\\vibe.d")); + } + return null; + } + + private { + Path[] m_includePaths; + } +} diff --git a/source/dub/packagesupplier.d b/source/dub/packagesupplier.d index 358debf..b3f6edf 100644 --- a/source/dub/packagesupplier.d +++ b/source/dub/packagesupplier.d @@ -1,5 +1,5 @@ /** - A package manager. + A package supplier, able to get some packages to the local FS. Copyright: © 2012 Matthias Dondorff License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.