diff --git a/source/dub/generators/compiler.d b/source/dub/generators/compiler.d new file mode 100644 index 0000000..52056b9 --- /dev/null +++ b/source/dub/generators/compiler.d @@ -0,0 +1,11 @@ +/** + + 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.compiler; + +struct Compiler { + +} \ No newline at end of file diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index 3374bb6..8d30215 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -1,371 +1,446 @@ -/** - 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.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.dub; -import dub.package_; -import dub.packagemanager; -import dub.generators.generator; - -// version = VISUALD_SEPERATE_PROJECT_FILES; -version = VISUALD_SINGLE_PROJECT_FILE; - -class VisualDGenerator : ProjectGenerator { - private { - Application m_app; - PackageManager m_pkgMgr; - string[string] m_projectUuids; - bool[string] m_generatedProjects; - } - - this(Application 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 { - 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); - version(VISUALD_SEPERATE_PROJECT_FILES) { - performOnDependencies(main, (const Package pack) { generateSolutionEntries(ret, pack); } ); - } - version(VISUALD_SINGLE_PROJECT_FILE) { - enforce(main == m_app.mainPackage()); - } - } - - 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); - - version(VISUALD_SEPERATE_PROJECT_FILES) { - 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); - - version(VISUALD_SEPERATE_PROJECT_FILES) - { - 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.Debug); - generateProjectConfiguration(ret, pack, Config.Release); - // generateProjectConfiguration(ret, pack, Config.Unittest); - - // Add all files - // TODO: nice folders - struct SourceFile { - string pkg; - Path structurePath; - Path filePath; - int opCmp(ref const SourceFile rhs) const { return filePath.opCmp(rhs.filePath); } - } - bool[SourceFile] sourceFiles; - void gatherSources(const(Package) package_, string prefix) { - logDebug("Gather sources for %s", package_.name); - if(prefix != "") prefix = "|" ~ prefix ~ "|"; - foreach(source; package_.sources) { - SourceFile f = { package_.name, source, source }; - sourceFiles[f] = true; - logDebug("pkg file: %s", source); - } - } - version(VISUALD_SINGLE_PROJECT_FILE) { - // gather all sources - enforce(pack == m_app.mainPackage(), "Some setup has gone wrong in VisualD.generateProj()"); - bool[string] gathered; - void gatherAll(const Package package_) { - logDebug("Looking at %s", package_.name); - if(package_.name in gathered) - return; - gathered[package_.name] = true; - gatherSources(package_, ""/*package_.name*/); - performOnDependencies(package_, (const Package dependency) { gatherAll(dependency); }); - } - gatherAll(pack); - } - version(VISUALD_SEPERATE_PROJECT_FILES) { - // gather sources for this package only - gatherSources(pack, ""); - } - - // Create folders and files - // TODO: nice foldering - version(VISUALD_SINGLE_PROJECT_FILE) { - SourceFile[] files = sourceFiles.keys; - sort!("a.pkg > b.pkg")(files); - string last = ""; - ret.formattedWrite(" - "); - foreach(source; files) { - if(last != source.pkg) { - if(!last.empty) - ret.formattedWrite(" - "); - ret.formattedWrite(" - ", source.pkg); - last = source.pkg; - } - ret.formattedWrite(" - ", source.filePath.toString()); - } - ret.formattedWrite(" - - "); - } - version(VISUALD_SEPERATE_PROJECT_FILES) { - formattedWrite(ret, " - ", projName); - foreach(source, dummy; sourceFiles) - ret.formattedWrite("\n ", source.filePath.toString()); - ret.formattedWrite(" - "); - } - 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 - 0 - 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 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - $(DMDInstallDir)windows\\bin\\dmd.exe - - - $(ConfigurationName) - $(OutDir) - - - 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 - - - - *.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) { - 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]; - } - } +/** + 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.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.dub; +import dub.package_; +import dub.packagemanager; +import dub.generators.generator; + +// version = VISUALD_SEPERATE_PROJECT_FILES; +version = VISUALD_SINGLE_PROJECT_FILE; + +class VisualDGenerator : ProjectGenerator { + private { + Application m_app; + PackageManager m_pkgMgr; + string[string] m_projectUuids; + bool[string] m_generatedProjects; + } + + this(Application 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 { + 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); + version(VISUALD_SEPERATE_PROJECT_FILES) { + performOnDependencies(main, (const Package pack) { generateSolutionEntries(ret, pack); } ); + } + version(VISUALD_SINGLE_PROJECT_FILE) { + enforce(main == m_app.mainPackage()); + } + } + + 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); + + version(VISUALD_SEPERATE_PROJECT_FILES) { + 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); + + version(VISUALD_SEPERATE_PROJECT_FILES) + { + 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.Debug); + generateProjectConfiguration(ret, pack, Config.Release); + // generateProjectConfiguration(ret, pack, Config.Unittest); + + // Add all files + // TODO: nice folders + struct SourceFile { + string pkg; + Path structurePath; + Path filePath; + int opCmp(ref const SourceFile rhs) const { return filePath.opCmp(rhs.filePath); } + } + bool[SourceFile] sourceFiles; + void gatherSources(const(Package) package_, string prefix) { + logDebug("Gather sources for %s", package_.name); + if(prefix != "") prefix = "|" ~ prefix ~ "|"; + foreach(source; package_.sources) { + SourceFile f = { package_.name, source, source }; + sourceFiles[f] = true; + logDebug("pkg file: %s", source); + } + } + + version(VISUALD_SINGLE_PROJECT_FILE) { + // gather all sources + enforce(pack == m_app.mainPackage(), "Some setup has gone wrong in VisualD.generateProj()"); + bool[string] gathered; + void gatherAll(const Package package_) { + logDebug("Looking at %s", package_.name); + if(package_.name in gathered) + return; + gathered[package_.name] = true; + gatherSources(package_, ""/*package_.name*/); + performOnDependencies(package_, (const Package dependency) { gatherAll(dependency); }); + } + gatherAll(pack); + } + version(VISUALD_SEPERATE_PROJECT_FILES) { + // gather sources for this package only + gatherSources(pack, ""); + } + + // Create folders and files + // TODO: nice foldering + version(VISUALD_SINGLE_PROJECT_FILE) { + SourceFile[] files = sourceFiles.keys; + sort!("a.pkg > b.pkg")(files); + string last = ""; + ret.formattedWrite(" + "); + foreach(source; files) { + if(last != source.pkg) { + if(!last.empty) + ret.formattedWrite(" + "); + ret.formattedWrite(" + ", source.pkg); + last = source.pkg; + } + ret.formattedWrite(" + ", source.filePath.toString()); + } + ret.formattedWrite(" + + "); + } + version(VISUALD_SEPERATE_PROJECT_FILES) { + formattedWrite(ret, " + ", projName); + foreach(source, dummy; sourceFiles) + ret.formattedWrite("\n ", source.filePath.toString()); + ret.formattedWrite(" + "); + } + 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) { + + + string[] getSettingsHelper(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 = getSettingsHelper(pack.getBuildSettings(platform, ""), setting); + foreach(i; itms) + ret ~= to!string(pack.path) ~ "\\" ~ i; + } + else + ret ~= getSettingsHelper(pack.getBuildSettings(platform, ""), setting); + return ret; + } + } + + string combine(string seperator)(string[] vals) { + return reduce!("a~'"~seperator~"'~b")("", vals); + } + + // Specify build configuration name + ret.formattedWrite(" + ", to!string(type)); + + ret.formattedWrite(" + 0 + 0 + 0 + 0 + 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 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0"); + + // Compiler ? + ret.formattedWrite(" + $(DMDInstallDir)windows\\bin\\dmd.exe + + + $(ConfigurationName) + $(OutDir) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\\$(TargetName).json + 0 + + 0"); + + // Add version identifiers + string versions = combine!(" ")(getSettings(pack, "versions", false)); + ret.formattedWrite(" + %s", versions); + + ret.formattedWrite(" + 0 + 0 + 0 + + + + 0 + "); + + // TODO: Mago? Debugger settings! + ret.formattedWrite(" + 1 + $(VisualDInstallDir)cv2pdb\\cv2pdb.exe + 0 + 0 + 0 + + + "); + + // Add libraries. + string linkLibs = combine!(" ")(getSettings(pack, "libs", false)); + string addLinkFiles = combine!(" ")(getSettings(pack, "files", true)); + ret.formattedWrite(" + %s", linkLibs ~ " " ~ addLinkFiles); + + // Add library paths ( not necessary, libraries have absolute path ) + ret.formattedWrite(" + + + + bin\\$(ProjectName)_d.exe + + "); + + // Add a post build command to copy files + ret.formattedWrite(" + "); + + ret.formattedWrite(" + *.obj;*.cmd;*.build;*.json;*.dep + "); + } + + void performOnDependencies(const Package main, void delegate(const Package pack) op) { + // TODO: cyclic check + + 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]; + } + + string libfiles(const Package pack) { + return ""; + } + } } \ No newline at end of file diff --git a/source/dub/package_.d b/source/dub/package_.d index 5321ea6..c80ce2c 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -1,240 +1,283 @@ -/** - Stuff with dependencies. - - 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.package_; - -import dub.dependency; -import dub.utils; - -import std.array; -import std.conv; -import std.exception; -import std.file; -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: -// { -// "name": "MetalCollection", -// "author": "VariousArtists", -// "version": "1.0.0", -// "url": "https://github.org/...", -// "keywords": "a,b,c", -// "category": "music.best", -// "dependencies": { -// "black-sabbath": ">=1.0.0", -// "CowboysFromHell": "<1.0.0", -// "BeneathTheRemains": {"version": "0.4.1", "path": "./beneath-0.4.1"} -// } -// "licenses": { -// ... -// } -// } -class Package { - static struct LocalPackageDef { string name; Version version_; Path path; } - - private { - InstallLocation m_location; - Path m_path; - Json m_meta; - Dependency[string] m_dependencies; - LocalPackageDef[] m_localPackageDefs; - } - - this(InstallLocation location, Path root) - { - this(jsonFromFile(root ~ PackageJsonFilename), location, root); - } - - 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 ~= LocalPackageDef(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 const(LocalPackageDef)[] 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( 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 { - Path[] allSources; - foreach(DirEntry d; dirEntries(to!string(m_path ~ Path("source")), "*.d", SpanMode.depth)) - allSources ~= Path(d.name); - 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"] ~ "'"; - s ~= "\n Dependencies:"; - foreach(string p, ref const Dependency v; m_dependencies) - s ~= "\n " ~ p ~ ", version '" ~ to!string(v) ~ "'"; - return s; - } - - /// direct access to the json of this package - @property ref Json json() { return m_meta; } - - /// Writes the json file back to the filesystem - void writeJson(Path path) { - 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 -} +/** + Stuff with dependencies. + + 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.package_; + +import dub.dependency; +import dub.utils; + +import std.array; +import std.conv; +import std.exception; +import std.file; +import vibe.core.file; +import vibe.data.json; +import vibe.inet.url; + +enum PackageJsonFilename = "package.json"; + +/// Represents a platform a package can be build upon. +struct BuildPlatform { + /// e.g. ["posix", "windows"] + string[] platform; + /// e.g. ["x86", "x64"] + string[] architecture; + /// e.g. "dmd" + string compiler; +} + +/// BuildPlatform specific settings, like needed libraries or additional +/// include paths. +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); } + + // Adds vals to arr without adding duplicates. + 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; + } + } + + // Parses json and returns the values of the corresponding field + // by the platform. + 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; + } +} + +/// Based on the BuildPlatform, creates an iterator with all suffixes. +/// +/// Suffixes are build upon the following scheme, where each component +/// is optional (indicated by []), but the order is obligatory. +/// "[-platform][-architecture][-compiler]" +/// +/// So the following strings are valid suffixes: +/// "-windows-x86-dmd" +/// "-dmd" +/// "-arm" +/// +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; +} + +/// Indicates where a package has been or should be installed to. +enum InstallLocation { + Local, + ProjectLocal, + UserWide, + SystemWide +} + +/// Representing an installed package, usually constructed from a json object. +/// +/// Json file example: +/// { +/// "name": "MetalCollection", +/// "author": "VariousArtists", +/// "version": "1.0.0", +/// "url": "https://github.org/...", +/// "keywords": "a,b,c", +/// "category": "music.best", +/// "dependencies": { +/// "black-sabbath": ">=1.0.0", +/// "CowboysFromHell": "<1.0.0", +/// "BeneathTheRemains": {"version": "0.4.1", "path": "./beneath-0.4.1"} +/// } +/// "licenses": { +/// ... +/// } +/// "configurations": { +// TODO: what and how? +/// } +// TODO: plain like this or packed together? +/// " +/// "dflags-X" +/// "lflags-X" +/// "libs-X" +/// "files-X" +/// "copyFiles-X" +/// "versions-X" +/// "importPaths-X" +/// "stringImportPaths-X" +/// +/// } +/// } +/// +/// TODO: explain configurations +class Package { + static struct LocalPackageDef { string name; Version version_; Path path; } + + private { + InstallLocation m_location; + Path m_path; + Json m_meta; + Dependency[string] m_dependencies; + LocalPackageDef[] m_localPackageDefs; + } + + this(InstallLocation location, Path root) + { + this(jsonFromFile(root ~ PackageJsonFilename), location, root); + } + + this(Json packageInfo, InstallLocation location = InstallLocation.Local, Path root = Path()) + { + m_location = location; + m_path = root; + m_meta = packageInfo; + + // extract dependencies and local package definitions + if( auto pd = "dependencies" in packageInfo ){ + 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 ~= LocalPackageDef(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 const(LocalPackageDef)[] 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( string k, _; *pv ) + ret.put(k); + return ret.data; + } + + /// Returns all BuildSettings for the given platform and config. + 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; + } + + /// Returns all sources as absolute paths. + @property const(Path[]) sources() const { + Path[] allSources; + foreach(DirEntry d; dirEntries(to!string(m_path ~ Path("source")), "*.d", SpanMode.depth)) + allSources ~= Path(d.name); + return allSources; + } + + /// TODO: what is the defaul configuration? + 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; + } + + /// Humanly readible information of this package and its dependencies. + string info() const { + string s; + s ~= cast(string)m_meta["name"] ~ ", version '" ~ cast(string)m_meta["version"] ~ "'"; + s ~= "\n Dependencies:"; + foreach(string p, ref const Dependency v; m_dependencies) + s ~= "\n " ~ p ~ ", version '" ~ to!string(v) ~ "'"; + return s; + } + + /// direct access to the json of this package + @property ref Json json() { return m_meta; } + + /// Writes the json file back to the filesystem + void writeJson(Path path) { + auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc); + scope(exit) dstFile.close(); + Appender!string js; + toPrettyJson(js, m_meta); + dstFile.write( js.data ); + } +}