diff --git a/source/dub/commandline.d b/source/dub/commandline.d index c453861..0d61462 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -146,8 +146,13 @@ string cmdname; args = common_args.extractRemainingArgs(); if (args.length >= 1 && !args[0].startsWith("-")) { - cmdname = args[0]; - args = args[1 .. $]; + if (args[0].endsWith(".d")) { + cmdname = "run"; + args = "--single" ~ args; + } else { + cmdname = args[0]; + args = args[1 .. $]; + } } else { if (options.help) { showHelp(commands, common_args); @@ -543,6 +548,7 @@ string m_defaultConfig; bool m_nodeps; bool m_forceRemove = false; + bool m_single; } override void prepare(scope CommandArgs args) @@ -576,6 +582,9 @@ "Specifies the way the compiler and linker are invoked. Valid values:", " separate (default), allAtOnce, singleFile" ]); + args.getopt("single", &m_single, [ + "Treats the package name as a filename. The file must contain a package recipe comment." + ]); } protected void setupPackage(Dub dub, string package_name, string default_build_type = "debug") @@ -607,10 +616,13 @@ // retrieve missing packages logDiagnostic("Checking for missing dependencies."); - dub.upgrade(UpgradeOptions.select); - // check for updates - logDiagnostic("Checking for upgrades."); - dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly|UpgradeOptions.useCachedResult); + if (m_single) dub.upgrade(UpgradeOptions.select | UpgradeOptions.noSaveSelections); + else { + dub.upgrade(UpgradeOptions.select); + + logDiagnostic("Checking for upgrades."); + dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly|UpgradeOptions.useCachedResult); + } } dub.project.validate(); @@ -618,6 +630,12 @@ private bool loadSpecificPackage(Dub dub, string package_name) { + if (m_single) { + enforce(package_name.length, "Missing file name of single-file package."); + dub.loadSingleFilePackage(package_name); + return true; + } + // load package in root_path to enable searching for sub packages if (loadCwdPackage(dub, package_name.length == 0)) { if (package_name.startsWith(":")) @@ -728,7 +746,7 @@ gensettings.runArgs = app_args; gensettings.force = m_force; gensettings.rdmd = m_rdmd; - gensettings.tempBuild = m_tempBuild; + gensettings.tempBuild = m_tempBuild || m_single; gensettings.parallelBuild = m_parallel; logDiagnostic("Generating using %s", m_generator); @@ -866,6 +884,7 @@ settings.buildSettings = m_buildSettings; settings.combined = m_combined; settings.force = m_force; + settings.tempBuild = m_single; settings.run = true; settings.runArgs = app_args; diff --git a/source/dub/dub.d b/source/dub/dub.d index fe2b342..1ff825e 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -171,18 +171,18 @@ private void init() { + import std.file : tempDir; 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/"; if(!m_userDubPath.absolute) m_userDubPath = Path(getcwd()) ~ m_userDubPath; - m_tempPath = Path("/tmp"); } + m_tempPath = Path(tempDir); m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true); m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true); @@ -247,6 +247,72 @@ m_project = new Project(m_packageManager, pack); } + /** Loads a single file package. + + Single-file packages are D files that contain a package receipe comment + at their top. A recipe comment must be a nested `/+ ... +/` style + comment, containing the virtual recipe file name and a colon, followed by the + recipe contents (what would normally be in dub.sdl/dub.json). + + Example: + --- + /+ dub.sdl: + name "test" + dependency "vibe-d" version="~>0.7.29" + +/ + import vibe.http.server; + + void main() + { + auto settings = new HTTPServerSettings; + settings.port = 8080; + listenHTTP(settings, &hello); + } + + void hello(HTTPServerRequest req, HTTPServerResponse res) + { + res.writeBody("Hello, World!"); + } + --- + + The script above can be invoked with "dub --single test.d". + */ + void loadSingleFilePackage(Path path) + { + import dub.recipe.io : parsePackageRecipe; + import std.file : mkdirRecurse, readText; + + path = makeAbsolute(path); + + string file_content = readText(path.toNativeString()); + auto idx = file_content.indexOf("/+"); + enforce(idx >= 0, "Missing /+ ... +/ recipe comment."); + file_content = file_content[idx+2 .. $]; + + idx = file_content.indexOf("+/"); + enforce(idx >= 0, "Missing closing \"+/\" for recipe comment."); + string recipe_content = file_content[0 .. idx]; + + idx = recipe_content.indexOf(':'); + enforce(idx > 0, "Missing recipe file name (e.g. \"dub.sdl:\") in recipe comment"); + auto recipe_filename = recipe_content[0 .. idx].strip(); + recipe_content = recipe_content[idx+1 .. $]; + + auto recipe = parsePackageRecipe(recipe_content, recipe_filename); + recipe.buildSettings.sourceFiles[""] = [path.toNativeString()]; + recipe.buildSettings.mainSourceFile = path.toNativeString(); + if (recipe.buildSettings.targetType == TargetType.autodetect) + recipe.buildSettings.targetType = TargetType.executable; + + auto pack = new Package(recipe, path.parentPath, null, "~master"); + loadPackage(pack); + } + /// ditto + void loadSingleFilePackage(string path) + { + loadSingleFilePackage(Path(path)); + } + /** Disables the default search paths and only searches a specific directory for packages. */ @@ -375,7 +441,7 @@ m_project.reinit(); - if (options & UpgradeOptions.select) + if ((options & UpgradeOptions.select) && !(options & UpgradeOptions.noSaveSelections)) m_project.saveSelections(); } @@ -1015,6 +1081,7 @@ select = 1<<4, /// Update the dub.selections.json file with the upgraded versions printUpgradesOnly = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence useCachedResult = 1<<6, /// Use cached information stored with the package to determine upgrades + noSaveSelections = 1<<7, /// Don't store updated selections on disk } /// Determines which of the default package suppliers are queried for packages. diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 57267d3..1873d91 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -146,7 +146,10 @@ private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, string build_id, in Package[] packages, in Path[] additional_dep_files) { auto cwd = Path(getcwd()); - auto target_path = pack.path ~ format(".dub/build/%s/", build_id); + + Path target_path; + if (settings.tempBuild) target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", pack.name, pack.version_, build_id); + else target_path = pack.path ~ format(".dub/build/%s/", build_id); if (!settings.force && isUpToDate(target_path, buildsettings, settings.platform, pack, packages, additional_dep_files)) { logInfo("%s %s: target for configuration \"%s\" is up to date.", pack.name, pack.version_, config); @@ -155,7 +158,7 @@ return true; } - if (settings.tempBuild || !isWritableDir(target_path, true)) { + if (!isWritableDir(target_path, true)) { if (!settings.tempBuild) logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString()); performDirectBuild(settings, buildsettings, pack, config);