/** 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.dub; import dub.compilers.compiler; import dub.dependency; import dub.installation; import dub.utils; import dub.registry; import dub.package_; import dub.packagemanager; import dub.packagesupplier; import dub.project; import dub.generators.generator; import vibecompat.core.file; import vibecompat.core.log; import vibecompat.data.json; import vibecompat.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; /// The default supplier for packages, which is the registry /// hosted by vibed.org. PackageSupplier defaultPackageSupplier() { Url url = Url.parse("http://registry.vibed.org/"); logDebug("Using the registry from %s", url); return new RegistryPS(url); } /// The Dub class helps in getting the applications /// dependencies up and running. An instance manages one application. class Dub { private { Path m_cwd, m_tempPath; Path m_root; PackageSupplier m_packageSupplier; Path m_userDubPath, m_systemDubPath; Json m_systemConfig, m_userConfig; PackageManager m_packageManager; Project m_app; } /// Initiales the package manager for the vibe application /// under root. this(PackageSupplier ps = defaultPackageSupplier()) { m_cwd = Path(getcwd()); version(Windows){ m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/"; m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/"; m_tempPath = Path(environment.get("TEMP")); } else version(Posix){ m_systemDubPath = Path("/var/lib/dub/"); m_userDubPath = Path(environment.get("HOME")) ~ ".dub/"; m_tempPath = Path("/tmp"); } m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true); m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true); m_packageSupplier = ps; m_packageManager = new PackageManager(m_systemDubPath ~ "packages/", m_userDubPath ~ "packages/"); } /// Returns the name listed in the package.json of the current /// application. @property string projectName() const { return m_app.name; } @property Path projectPath() const { return m_root; } @property string[] configurations() const { return m_app.configurations; } @property inout(PackageManager) packageManager() inout { return m_packageManager; } @property Path binaryPath() const { return m_app.binaryPath; } void loadPackageFromCwd() { m_root = m_cwd; m_packageManager.projectPackagePath = m_root ~ ".dub/packages/"; m_app = new Project(m_packageManager, m_root); } string getDefaultConfiguration(BuildPlatform platform) const { return m_app.getDefaultConfiguration(platform); } /// Lists all installed modules void list() { logInfo(m_app.info()); } /// Performs installation and uninstallation as necessary for /// the application. /// @param options bit combination of UpdateOptions bool update(UpdateOptions options) { Action[] actions = m_app.determineActions(m_packageSupplier, options); if( actions.length == 0 ) return true; logInfo("The following changes could be performed:"); bool conflictedOrFailed = false; foreach(Action a; actions) { logInfo(capitalize(to!string(a.type)) ~ ": " ~ a.packageId ~ ", version %s", a.vers); if( a.type == Action.Type.conflict || a.type == Action.Type.failure ) { logInfo("Issued by: "); conflictedOrFailed = true; foreach(string pkg, d; a.issuer) logInfo(" "~pkg~": %s", d); } } if( conflictedOrFailed || options & UpdateOptions.JustAnnotate ) return conflictedOrFailed; // Uninstall first // ?? // foreach(Action a ; filter!((Action a) => a.type == Action.Type.Uninstall)(actions)) // uninstall(a.packageId); // foreach(Action a; filter!((Action a) => a.type == Action.Type.InstallUpdate)(actions)) // install(a.packageId, a.vers); foreach(Action a; actions) if(a.type == Action.Type.uninstall){ assert(a.pack !is null, "No package specified for uninstall."); uninstall(a.pack); } foreach(Action a; actions) if(a.type == Action.Type.install) install(a.packageId, a.vers, a.location); m_app.reinit(); Action[] newActions = m_app.determineActions(m_packageSupplier, 0); if(newActions.length > 0) { logInfo("There are still some actions to perform:"); foreach(Action a; newActions) logInfo("%s", a); } else logInfo("You are up to date"); return newActions.length == 0; } /// Generate project files for a specified IDE. /// Any existing project files will be overridden. void generateProject(string ide, GeneratorSettings settings) { auto generator = createProjectGenerator(ide, m_app, m_packageManager); generator.generateProject(settings); } /// Creates a zip from the application. void createZip(string zipFile) { m_app.createZip(zipFile); } /// Prints some information to the log. void info() { logInfo("Status for %s", m_root); logInfo("\n" ~ m_app.info()); } /// Gets all installed packages as a "packageId" = "version" associative array 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 /// list of dependencies in the application's package.json void install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.ProjectLocal) { auto pinfo = m_packageSupplier.packageJson(packageId, dep); string ver = pinfo["version"].get!string; logInfo("Downloading %s %s...", packageId, ver); logDebug("Acquiring package zip file"); auto dload = m_root ~ ".dub/temp/downloads"; auto tempfname = packageId ~ "-" ~ (ver.startsWith('~') ? ver[1 .. $] : ver) ~ ".zip"; auto tempFile = m_tempPath ~ tempfname; if( existsFile(tempFile) ) removeFile(tempFile); m_packageSupplier.storePackage(tempFile, packageId, dep); // Q: continue on fail? scope(exit) removeFile(tempFile); logInfo("Installing %s %s...", packageId, ver); m_packageManager.install(tempFile, pinfo, location); } /// Uninstalls a given package from the list of installed modules. /// @removeFromApplication: if true, this will also remove an entry in the /// list of dependencies in the application's package.json void uninstall(in Package pack) { logInfo("Uninstalling %s in %s", pack.name, pack.path.toNativeString()); m_packageManager.uninstall(pack); } void addLocalPackage(string path, string ver, bool system) { auto abs_path = Path(path); if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; m_packageManager.addLocalPackage(abs_path, Version(ver), system ? LocalPackageType.system : LocalPackageType.user); } void removeLocalPackage(string path, bool system) { auto abs_path = Path(path); if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; m_packageManager.removeLocalPackage(abs_path, system ? LocalPackageType.system : LocalPackageType.user); } void createEmptyPackage(Path path) { path.normalize(); //Check to see if a target directory needs to be created if( !path.empty ){ if( !existsFile(path) ) createDirectory(path); } //Make sure we do not overwrite anything accidentally if( existsFile(path ~ PackageJsonFilename) || existsFile(path ~ "source") || existsFile(path ~ "views") || existsFile(path ~ "public") ) { throw new Exception("The current directory is not empty.\n"); } //raw strings must be unindented. immutable packageJson = `{ "name": "`~(path.empty ? "my-project" : path.head.toString())~`", "description": "An example project skeleton", "homepage": "http://example.org", "copyright": "Copyright © 2000, Your Name", "authors": [ "Your Name" ], "dependencies": { } } `; immutable appFile = `import std.stdio; void main() { writeln("Edit source/app.d to start your project."); } `; //Create the common directories. createDirectory(path ~ "source"); createDirectory(path ~ "views"); createDirectory(path ~ "public"); //Create the common files. openFile(path ~ PackageJsonFilename, FileMode.Append).write(packageJson); openFile(path ~ "source/app.d", FileMode.Append).write(appFile); //Act smug to the user. logInfo("Successfully created an empty project in '"~path.toNativeString()~"'."); } }