diff --git a/source/app.d b/source/app.d index f4ec8c5..09c4618 100644 --- a/source/app.d +++ b/source/app.d @@ -7,6 +7,7 @@ */ module app; +import dub.dependency; import dub.dub; import dub.platform; import dub.package_; @@ -37,6 +38,8 @@ LogLevel loglevel = LogLevel.Info; string build_type = "debug", build_config; bool print_platform, print_builds, print_configs; + bool install_system = false, install_local = false; + string install_version; getopt(args, "v|verbose", &verbose, "vverbose", &vverbose, @@ -49,7 +52,10 @@ "config", &build_config, "print-builds", &print_builds, "print-configs", &print_configs, - "print-platform", &print_platform + "print-platform", &print_platform, + "system", &install_system, + "local", &install_local, + "version", &install_version ); if( vverbose ) loglevel = LogLevel.Trace; @@ -74,7 +80,6 @@ return 0; } - auto appPath = getcwd(); Url registryUrl = Url.parse("http://registry.vibed.org/"); logDebug("Using dub registry url '%s'", registryUrl); @@ -92,6 +97,8 @@ logInfo(""); } + Dub dub = new Dub(new RegistryPS(registryUrl)); + // handle the command switch( cmd ){ default: @@ -107,8 +114,7 @@ break; case "run": case "build": - Dub dub = new Dub(Path(appPath), new RegistryPS(registryUrl)); - + dub.loadPackagefromCwd(); if( print_builds ){ logInfo("Available build types:"); foreach( tp; ["debug", "release", "unittest", "profile"] ) @@ -124,7 +130,7 @@ } if( !nodeps ){ - logInfo("Checking dependencies in '%s'", appPath); + logInfo("Checking dependencies in '%s'", dub.projectPath); logDebug("dub initialized"); dub.update(annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None); } @@ -214,11 +220,35 @@ break; case "upgrade": - logInfo("Upgrading application in '%s'", appPath); - Dub dub = new Dub(Path(appPath), new RegistryPS(registryUrl)); + dub.loadPackagefromCwd(); + logInfo("Upgrading project in '%s'", dub.projectPath); logDebug("dub initialized"); dub.update(UpdateOptions.Reinstall | (annotate ? UpdateOptions.JustAnnotate : UpdateOptions.None)); break; + case "install": + enforce(args.length >= 2, "Missing package name."); + auto location = InstallLocation.UserWide; + auto name = args[1]; + enforce(!install_local || !install_system, "Cannot install locally and system wide at the same time."); + if( install_local ) location = InstallLocation.Local; + else if( install_system ) location = InstallLocation.SystemWide; + if( install_version.length ) dub.install(name, new Dependency(install_version), location); + else { + try dub.install(name, new Dependency(">=0.0.0"), location); + catch(Exception) dub.install(name, new Dependency("~master"), location); + } + break; + case "uninstall": + enforce("Not implemented."); + break; + case "add-local": + enforce(args.length >= 3, "Missing arguments."); + dub.addLocalPackage(args[1], args[2], install_system); + break; + case "remove-local": + enforce(args.length >= 2, "Missing path to package."); + dub.removeLocalPackage(args[1], install_system); + break; } return 0; @@ -232,7 +262,6 @@ } } - private void showHelp(string command) { // This help is actually a mixup of help for this application and the @@ -249,23 +278,36 @@ run Compiles and runs the application (default command) build Just compiles the application in the project directory upgrade Forces an upgrade of all dependencies + install Manually installs a package + uninstall Uninstalls a package + add-local + Adds a local package directory (e.g. a git repository) + remove-local Removes a local package directory -Options: +General options: + --annotate Do not execute dependency installations, just print + -v --verbose Also output debug messages + --vverbose Also output trace messages (produces a lot of output) + -q --quiet Only output warnings and errors + --vquiet No output + +Build/run options: --build=NAME Specifies the type of build to perform. Valid names: debug (default), release, unittest, profile, docs, plain --config=NAME Builds the specified configuration. Configurations can be defined in package.json --nodeps Do not check dependencies for 'run' or 'build' - --annotate Do not execute dependency installations, just print --print-builds Prints the list of available build types --print-configs Prints the list of available configurations --print-platform Prints the identifiers for the current build platform as used for the build fields in package.json - -v --verbose Also output debug messages - --vverbose Also output trace messages (produces a lot of output) - -q --quiet Only output warnings and errors - --vquiet No output + +Install options: + --version Use the specified version/branch instead of the latest + --system Install system wide instead of user local + --local Install as in a sub folder of the current directory + `); } @@ -289,19 +331,16 @@ private string getBinName(const Dub dub) { - string ret; - if( dub.packageName.length > 0 ) - ret = dub.packageName(); - //Otherwise fallback to source/app.d - else ret ="app"; + // take the project name as the base or fall back to "app" + string ret = dub.projectName; + if( ret.length == 0 ) ret ="app"; version(Windows) { ret ~= ".exe"; } - return ret; } private Path getMainSourceFile(const Dub dub) { - auto p = Path("source") ~ (dub.packageName() ~ ".d"); + auto p = Path("source") ~ (dub.projectName ~ ".d"); return existsFile(p) ? p : Path("source/app.d"); } @@ -342,7 +381,7 @@ } `; //Make sure we do not overwrite anything accidentally - if( (existsFile(cwd ~ "package.json")) || + if( (existsFile(cwd ~ PackageJsonFilename)) || (existsFile(cwd ~ "source" )) || (existsFile(cwd ~ "views" )) || (existsFile(cwd ~ "public" ))) @@ -357,7 +396,7 @@ createDirectory(cwd ~ "views" ); createDirectory(cwd ~ "public"); //Create the common files. - openFile(cwd ~ "package.json", FileMode.Append).write(packageJson); + openFile(cwd ~ PackageJsonFilename, FileMode.Append).write(packageJson); openFile(cwd ~ "source/app.d", FileMode.Append).write(appFile); //Act smug to the user. logInfo("Successfully created empty project."); diff --git a/source/dub/dub.d b/source/dub/dub.d index 747e395..002db5e 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -127,8 +127,8 @@ try m_json = jsonFromFile(m_root ~ ".dub/dub.json", true); catch(Exception t) logDebug("Failed to read .dub/dub.json: %s", t.msg); - if(!exists(to!string(m_root~"package.json"))) { - logWarn("There was no 'package.json' found for the application in '%s'.", m_root); + if(!exists(to!string(m_root~PackageJsonFilename))) { + logWarn("There was no '"~PackageJsonFilename~"' found for the application in '%s'.", m_root); } else { m_main = new Package(InstallLocation.Local, m_root); foreach( name, vspec; m_main.dependencies ){ @@ -422,6 +422,7 @@ /// dependencies up and running. class Dub { private { + Path m_cwd, m_tempPath; Path m_root; PackageSupplier m_packageSupplier; Path m_userDubPath, m_systemDubPath; @@ -432,33 +433,42 @@ /// Initiales the package manager for the vibe application /// under root. - this(Path root, PackageSupplier ps = defaultPackageSupplier()) + this(PackageSupplier ps = defaultPackageSupplier()) { - assert(root.absolute, "Need an absolute path for the DUB package."); - m_root = root; + m_cwd = Path(getcwd()); version(Windows){ m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/"; m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/"; + m_tempPath = Path(environment.get("TEMP")); } else version(Posix){ m_systemDubPath = Path("/etc/dub/"); m_userDubPath = Path(environment.get("HOME")) ~ ".dub/"; + m_tempPath = Path("/tmp"); } m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true); m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true); - m_packageManager = new PackageManager(m_systemDubPath ~ "packages/", m_userDubPath ~ "packages/", root ~ ".dub/packages/"); - m_app = new Application(m_packageManager, root); 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 packageName() const { return m_app.name; } + @property string projectName() const { return m_app.name; } + + @property Path projectPath() const { return m_root; } @property string[] configurations() const { return m_app.configurations; } + void loadPackagefromCwd() + { + m_root = m_cwd; + m_packageManager.projectPackagePath = m_root ~ ".dub/packages/"; + m_app = new Application(m_packageManager, m_root); + } + /// Returns a list of flags which the application needs to be compiled /// properly. BuildSettings getBuildSettings(BuildPlatform platform, string config) { return m_app.getBuildSettings(platform, config); } @@ -545,9 +555,7 @@ logDebug("Aquiring package zip file"); auto dload = m_root ~ ".dub/temp/downloads"; - if(!exists(to!string(dload))) - mkdirRecurse(to!string(dload)); - auto tempFile = m_root ~ (".dub/temp/downloads/"~packageId~".zip"); + auto tempFile = m_tempPath ~ ("dub-download-"~packageId~"-"~ver~".zip"); string sTempFile = to!string(tempFile); if(exists(sTempFile)) remove(sTempFile); m_packageSupplier.storePackage(tempFile, packageId, dep); // Q: continue on fail? @@ -565,6 +573,20 @@ 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); + } + + 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); + } } private void processVars(ref BuildSettings dst, string project_path, BuildSettings settings) diff --git a/source/dub/package_.d b/source/dub/package_.d index 87c1730..1203db8 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -16,6 +16,8 @@ import vibe.data.json; import vibe.inet.url; +enum PackageJsonFilename = "package.json"; + struct BuildPlatform { string[] platform; string[] architecture; @@ -118,7 +120,7 @@ { m_location = location; m_path = root; - m_meta = jsonFromFile(root ~ "package.json"); + m_meta = jsonFromFile(root ~ PackageJsonFilename); m_dependencies = .dependencies(m_meta); } @@ -176,7 +178,7 @@ /// Writes the json file back to the filesystem void writeJson(Path path) { - auto dstFile = openFile((path~"package.json").toString(), FileMode.CreateTrunc); + auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc); scope(exit) dstFile.close(); Appender!string js; toPrettyJson(js, m_meta); diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index cdf5916..5a2758b 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -13,6 +13,7 @@ import dub.utils; import std.algorithm : countUntil, filter, sort; +import std.conv; import std.exception; import std.file; import std.zip; @@ -23,7 +24,8 @@ import vibe.stream.operations; -enum PackageJsonFileName = "package.json"; +enum JournalJsonFilename = "journal.json"; +enum LocalPackagesFilename = "local-packages.json"; class PackageManager { @@ -34,10 +36,11 @@ Package[][string] m_systemPackages; Package[][string] m_userPackages; Package[string] m_projectPackages; - Package[] m_localPackages; + Package[] m_localUserPackages; + Package[] m_localSystemPackages; } - this(Path system_package_path, Path user_package_path, Path project_package_path) + this(Path system_package_path, Path user_package_path, Path project_package_path = Path()) { m_systemPackagePath = system_package_path; m_userPackagePath = user_package_path; @@ -45,6 +48,9 @@ refresh(); } + @property Path projectPackagePath() const { return m_projectPackagePath; } + @property void projectPackagePath(Path path) { m_projectPackagePath = path; refresh(); } + Package getPackage(string name, Version ver) { foreach( p; getPackageIterator(name) ) @@ -76,7 +82,12 @@ if( auto ret = del(*pp) ) return ret; // then local packages - foreach( p; m_localPackages ) + foreach( p; m_localUserPackages ) + if( p.name == name ) + if( auto ret = del(p) ) return ret; + + // then local packages + foreach( p; m_localSystemPackages ) if( p.name == name ) if( auto ret = del(p) ) return ret; @@ -106,7 +117,7 @@ Path destination; final switch( location ){ case InstallLocation.Local: destination = Path(package_name); break; - case InstallLocation.ProjectLocal: destination = m_projectPackagePath ~ package_name; break; + case InstallLocation.ProjectLocal: enforce(!m_projectPackagePath.empty, "no project path set."); destination = m_projectPackagePath ~ package_name; break; case InstallLocation.UserWide: destination = m_userPackagePath ~ (package_name ~ "/" ~ package_version); break; case InstallLocation.SystemWide: destination = m_systemPackagePath ~ (package_name ~ "/" ~ package_version); break; } @@ -127,7 +138,7 @@ // In a github zip, the actual contents are in a subfolder Path zip_prefix; foreach(ArchiveMember am; archive.directory) - if( Path(am.name).head == PathEntry("package.json") ){ + if( Path(am.name).head == PathEntry(PackageJsonFilename) ){ zip_prefix = Path(am.name)[0 .. 1]; break; } @@ -171,20 +182,17 @@ } } - { // overwrite package.json (this one includes a version field) - Json pi = jsonFromFile(destination~"package.json"); - auto pj = openFile(destination~"package.json", FileMode.CreateTrunc); - scope(exit) pj.close(); - pi["version"] = package_info["version"]; - toPrettyJson(pj, pi); - } + // overwrite package.json (this one includes a version field) + Json pi = jsonFromFile(destination~PackageJsonFilename); + pi["version"] = package_info["version"]; + writeJsonFile(destination~PackageJsonFilename, pi); // Write journal logTrace("Saving installation journal..."); - journal.add(Journal.Entry(Journal.Type.RegularFile, Path("journal.json"))); - journal.save(destination ~ "journal.json"); + journal.add(Journal.Entry(Journal.Type.RegularFile, Path(JournalJsonFilename))); + journal.save(destination ~ JournalJsonFilename); - if( existsFile(destination~"package.json") ) + if( existsFile(destination~PackageJsonFilename) ) logInfo("%s has been installed with version %s", package_name, package_version); auto pack = new Package(location, destination); @@ -227,7 +235,7 @@ } // delete package files physically - auto journalFile = pack.path~"journal.json"; + auto journalFile = pack.path~JournalJsonFilename; if( !existsFile(journalFile) ) throw new Exception("Uninstall failed, no journal found for '"~pack.name~"'. Please uninstall manually."); @@ -266,65 +274,89 @@ logInfo("Uninstalled package: '"~pack.name~"'"); } + void addLocalPackage(Path path, Version ver, bool system) + { + Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; + auto info = jsonFromFile(path ~ PackageJsonFilename, false); + string name; + if( "name" !in info ) info["name"] = path.head.toString(); + info["version"] = ver.toString(); + + *packs ~= new Package(info, InstallLocation.Local, path); + + writeLocalPackageList(system); + } + + void removeLocalPackage(Path path, bool system) + { + Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; + size_t[] to_remove; + foreach( i, entry; *packs ) + if( entry.path == path ) + to_remove ~= i; + enforce(to_remove.length > 0, "No "~(system?"system":"user")~" package found at "~path.toNativeString()); + + foreach_reverse( i; to_remove ) + *packs = (*packs)[0 .. i] ~ (*packs)[i+1 .. $]; + + writeLocalPackageList(system); + } + + void writeLocalPackageList(bool system) + { + Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; + Json[] newlist; + foreach( p; *packs ){ + auto entry = Json.EmptyObject; + entry["name"] = p.name; + entry["version"] = p.ver.toString(); + entry["path"] = p.path.toNativeString(); + newlist ~= entry; + } + writeJsonFile((system ? m_systemPackagePath : m_userPackagePath) ~ LocalPackagesFilename, Json(newlist)); + } + void refresh() { - // rescan the system package folder - m_systemPackages = null; - if( m_systemPackagePath.existsDirectory() ){ - logDebug("iterating dir %s", m_systemPackagePath.toNativeString()); - try foreach( pdir; iterateDirectory(m_systemPackagePath) ){ - logDebug("iterating dir %s entry %s", m_systemPackagePath.toNativeString(), pdir.name); - if( !pdir.isDirectory ) continue; - Package[] vers; - auto pack_path = m_systemPackagePath ~ pdir.name; - foreach( vdir; iterateDirectory(pack_path) ){ - if( !vdir.isDirectory ) continue; - auto ver_path = pack_path ~ vdir.name; - if( !existsFile(ver_path ~ PackageJsonFileName) ) continue; - try { - auto p = new Package(InstallLocation.SystemWide, ver_path); - vers ~= p; - } catch( Exception e ){ - logError("Failed to load package in %s: %s", ver_path, e.msg); + // rescan the system and user package folder + void scanPackageFolder(Path path, ref Package[][string] packs, InstallLocation location) + { + packs = null; + if( path.existsDirectory() ){ + logDebug("iterating dir %s", path.toNativeString()); + try foreach( pdir; iterateDirectory(path) ){ + logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name); + if( !pdir.isDirectory ) continue; + Package[] vers; + auto pack_path = path ~ pdir.name; + foreach( vdir; iterateDirectory(pack_path) ){ + if( !vdir.isDirectory ) continue; + auto ver_path = pack_path ~ vdir.name; + if( !existsFile(ver_path ~ PackageJsonFilename) ) continue; + try { + auto p = new Package(location, ver_path); + vers ~= p; + } catch( Exception e ){ + logError("Failed to load package in %s: %s", ver_path, e.msg); + } } + packs[pdir.name] = vers; } - m_systemPackages[pdir.name] = vers; + catch(Exception e) logDebug("Failed to enumerate %s packages: %s", to!string(location), e.toString()); } - catch(Exception e) logDebug("Failed to enumerate system packages: %s", e.toString()); } + scanPackageFolder(m_systemPackagePath, m_systemPackages, InstallLocation.SystemWide); + scanPackageFolder(m_userPackagePath, m_userPackages, InstallLocation.UserWide); - // rescan the user package folder - m_userPackages = null; - if( m_systemPackagePath.existsDirectory() ){ - logDebug("iterating dir %s", m_userPackagePath.toNativeString()); - try foreach( pdir; m_userPackagePath.iterateDirectory() ){ - if( !pdir.isDirectory ) continue; - Package[] vers; - auto pack_path = m_userPackagePath ~ pdir.name; - foreach( vdir; pack_path.iterateDirectory() ){ - if( !vdir.isDirectory ) continue; - auto ver_path = pack_path ~ vdir.name; - if( !existsFile(ver_path ~ PackageJsonFileName) ) continue; - try { - auto p = new Package(InstallLocation.UserWide, ver_path); - vers ~= p; - } catch( Exception e ){ - logError("Failed to load package in %s: %s", ver_path, e.msg); - } - } - m_userPackages[pdir.name] = vers; - } - catch(Exception e) logDebug("Failed to enumerate user packages: %s", e.toString()); - } // rescan the project package folder m_projectPackages = null; - if( m_projectPackagePath.existsDirectory() ){ + if( !m_projectPackagePath.empty && m_projectPackagePath.existsDirectory() ){ logDebug("iterating dir %s", m_projectPackagePath.toNativeString()); try foreach( pdir; m_projectPackagePath.iterateDirectory() ){ if( !pdir.isDirectory ) continue; auto pack_path = m_projectPackagePath ~ pdir.name; - if( !existsFile(pack_path ~ PackageJsonFileName) ) continue; + if( !existsFile(pack_path ~ PackageJsonFilename) ) continue; try { auto p = new Package(InstallLocation.ProjectLocal, pack_path); @@ -337,26 +369,26 @@ } // load locally defined packages - foreach( list_path; [m_systemPackagePath, m_userPackagePath] ){ + void scanLocalPackages(Path list_path, ref Package[] packs){ try { logDebug("Looking for local package map at %s", list_path.toNativeString()); - if( !existsFile(list_path ~ "local-packages.json") ) continue; + if( !existsFile(list_path ~ LocalPackagesFilename) ) return; logDebug("Try to load local package map at %s", list_path.toNativeString()); - auto packlist = jsonFromFile(list_path ~ "local-packages.json"); - enforce(packlist.type == Json.Type.Array, "local-packages.json must contain an array."); + auto packlist = jsonFromFile(list_path ~ LocalPackagesFilename); + enforce(packlist.type == Json.Type.Array, LocalPackagesFilename~" must contain an array."); foreach( pentry; packlist ){ try { auto name = pentry.name.get!string(); auto ver = pentry["version"].get!string(); auto path = Path(pentry.path.get!string()); auto info = Json.EmptyObject; - if( existsFile(path ~ "package.json") ) info = jsonFromFile(path ~ "package.json"); + if( existsFile(path ~ PackageJsonFilename) ) info = jsonFromFile(path ~ PackageJsonFilename); if( "name" in info && info.name.get!string() != name ) logWarn("Local package at %s has different name than %s (%s)", path.toNativeString(), name, info.name.get!string()); info.name = name; info["version"] = ver; auto pp = new Package(info, InstallLocation.Local, path); - m_localPackages ~= pp; + packs ~= pp; } catch( Exception e ){ logWarn("Error adding local package: %s", e.msg); } @@ -365,5 +397,7 @@ logDebug("Loading of local package list at %s failed: %s", list_path.toNativeString(), e.msg); } } + scanLocalPackages(m_systemPackagePath, m_localSystemPackages); + scanLocalPackages(m_userPackagePath, m_localUserPackages); } } \ No newline at end of file diff --git a/source/dub/utils.d b/source/dub/utils.d index 63e785e..d890bf4 100644 --- a/source/dub/utils.d +++ b/source/dub/utils.d @@ -48,6 +48,13 @@ return parseJson(text); } +package void writeJsonFile(Path path, Json json) +{ + auto f = openFile(path, FileMode.CreateTrunc); + scope(exit) f.close(); + toPrettyJson(f, json); +} + package bool isPathFromZip(string p) { enforce(p.length > 0); return p[$-1] == '/';