diff --git a/source/dub/commandline.d b/source/dub/commandline.d index 8a83c70..9a3135f 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -364,8 +364,9 @@ class InitCommand : Command { private{ - string m_buildType = "minimal"; + string m_templateType = "minimal"; PackageFormat m_format = PackageFormat.json; + bool m_nonInteractive; } this() { @@ -379,7 +380,7 @@ override void prepare(scope CommandArgs args) { - args.getopt("t|type", &m_buildType, [ + args.getopt("t|type", &m_templateType, [ "Set the type of project to generate. Available types:", "", "minimal - simple \"hello world\" project (default)", @@ -390,6 +391,7 @@ "Sets the format to use for the package description file. Possible values:", " " ~ [__traits(allMembers, PackageFormat)].map!(f => f == m_format.init.to!string ? f ~ " (default)" : f).join(", ") ]); + args.getopt("n|non-interactive", &m_nonInteractive, ["Don't enter interactive mode."]); } override int execute(Dub dub, string[] free_args, string[] app_args) @@ -401,18 +403,63 @@ dir = free_args[0]; free_args = free_args[1 .. $]; } + + string input(string caption, string default_value) + { + writef("%s [%s]: ", caption, default_value); + auto inp = readln(); + return inp.length > 1 ? inp[0 .. $-1] : default_value; + } + + void depCallback(ref PackageRecipe p, ref PackageFormat fmt) { + if (m_nonInteractive) return; + + while (true) { + string rawfmt = input("Package recipe format (sdl/json)", fmt.to!string); + if (!rawfmt.length) break; + try { + fmt = rawfmt.to!PackageFormat; + break; + } catch (Exception) { + logError("Invalid format, \""~rawfmt~"\", enter either \"sdl\" or \"json\"."); + } + } + auto author = p.authors.join(", "); + p.name = input("Name", p.name); + p.description = input("Description", p.description); + p.authors = input("Author name", author).split(",").map!(a => a.strip).array; + p.license = input("License", p.license); + p.copyright = input("Copyright string", p.copyright); + + while (true) { + auto depname = input("Add dependency (leave empty to skip)", null); + if (!depname.length) break; + try { + auto ver = dub.getLatestVersion(depname); + auto dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); + p.buildSettings.dependencies[depname] = dep; + logInfo("Added dependency %s %s", depname, dep.versionString); + } catch (Exception e) { + logError("Could not find package '%s'.", depname); + logDebug("Full error: %s", e.toString().sanitize); + } + } + } + //TODO: Remove this block in next version // Checks if argument uses current method of specifying project type. if (free_args.length) { if (["vibe.d", "deimos", "minimal"].canFind(free_args[0])) { - m_buildType = free_args[0]; + m_templateType = free_args[0]; free_args = free_args[1 .. $]; logInfo("Deprecated use of init type. Use --type=[vibe.d | deimos | minimal] in future."); } } - dub.createEmptyPackage(Path(dir), free_args, m_buildType, m_format); + dub.createEmptyPackage(Path(dir), free_args, m_templateType, m_format, &depCallback); + + logInfo("Package sucessfully created in %s", dir.length ? dir : "."); return 0; } } diff --git a/source/dub/dependency.d b/source/dub/dependency.d index c5e8b3c..4fa71ef 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -85,13 +85,30 @@ if (m_versA == m_versB && m_inclusiveA && m_inclusiveB) { // Special "==" case - if (m_versA == Version.MASTER ) r = "~master"; - else r = m_versA.toString(); - } else { - if (m_versA != Version.RELEASE) r = (m_inclusiveA ? ">=" : ">") ~ m_versA.toString(); - if (m_versB != Version.HEAD) r ~= (r.length==0 ? "" : " ") ~ (m_inclusiveB ? "<=" : "<") ~ m_versB.toString(); - if (m_versA == Version.RELEASE && m_versB == Version.HEAD) r = ">=0.0.0"; + if (m_versA == Version.MASTER ) return "~master"; + else return m_versA.toString(); } + + // "~>" case + if (m_inclusiveA && !m_inclusiveB && !m_versA.isBranch) { + auto vs = m_versA.toString(); + auto i1 = std.string.indexOf(vs, '-'), i2 = std.string.indexOf(vs, '+'); + auto i12 = i1 >= 0 ? i2 >= 0 ? i1 < i2 ? i1 : i2 : i1 : i2; + auto va = i12 >= 0 ? vs[0 .. i12] : vs; + auto parts = va.splitter('.').array; + assert(parts.length == 3, "Version string with a digit group count != 3: "~va); + + foreach (i; 0 .. 3) { + auto vp = parts[0 .. i+1].join("."); + auto ve = Version(expandVersion(vp)); + auto veb = Version(expandVersion(bumpVersion(vp))); + if (ve == m_versA && veb == m_versB) return "~>" ~ vp; + } + } + + if (m_versA != Version.RELEASE) r = (m_inclusiveA ? ">=" : ">") ~ m_versA.toString(); + if (m_versB != Version.HEAD) r ~= (r.length==0 ? "" : " ") ~ (m_inclusiveB ? "<=" : "<") ~ m_versB.toString(); + if (m_versA == Version.RELEASE && m_versB == Version.HEAD) r = ">=0.0.0"; return r; } @@ -467,6 +484,13 @@ logDebug("Dependency Unittest sucess."); } +unittest { + assert(Dependency("~>1.0.4").versionString == "~>1.0.4"); + assert(Dependency("~>1.4").versionString == "~>1.4"); + assert(Dependency("~>2").versionString == "~>2"); + assert(Dependency("~>1.0.4+1.2.3").versionString == "~>1.0.4"); +} + /** A version in the format "major.update.bugfix-prerelease+buildmetadata" diff --git a/source/dub/dub.d b/source/dub/dub.d index dd6be06..c513864 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -702,24 +702,68 @@ .filter!(t => t[1].length); } - void createEmptyPackage(Path path, string[] deps, string type, PackageFormat format = PackageFormat.sdl) + /** Returns a list of all available versions (including branches) for a + particular package. + + The list returned is based on the registered package suppliers. Local + packages are not queried in the search for versions. + + See_also: `getLatestVersion` + */ + Version[] listPackageVersions(string name) + { + Version[] versions; + foreach (ps; this.m_packageSuppliers) { + try versions ~= ps.getVersions(name); + catch (Exception e) { + logDebug("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg); + } + } + return versions.sort().uniq.array; + } + + /** Returns the latest available version for a particular package. + + This function returns the latest numbered version of a package. If no + numbered versions are available, it will return an available branch, + preferring "~master". + + Params: + package_name: The name of the package in question. + prefer_stable: If set to `true` (the default), returns the latest + stable version, even if there are newer pre-release versions. + + See_also: `listPackageVersions` + */ + Version getLatestVersion(string package_name, bool prefer_stable = true) + { + auto vers = listPackageVersions(package_name); + enforce(!vers.empty, "Failed to find any valid versions for a package name of '"~package_name~"'."); + auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array; + if (prefer_stable && final_versions.length) return final_versions[$-1]; + else if (vers[$-1].isBranch) return vers[$-1]; + else return vers[$-1]; + } + + void createEmptyPackage(Path path, string[] deps, string type, + PackageFormat format = PackageFormat.sdl, + scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null) { if (!path.absolute) path = m_rootPath ~ path; path.normalize(); - if (m_dryRun) return; string[string] depVers; string[] notFound; // keep track of any failed packages in here - foreach(ps; this.m_packageSuppliers){ - foreach(dep; deps){ - try{ - auto versionStrings = ps.getVersions(dep); - depVers[dep] = versionStrings[$-1].toString; - } catch(Exception e){ - notFound ~= dep; - } + foreach (dep; deps) { + Version ver; + try { + ver = getLatestVersion(dep); + depVers[dep] = ver.isBranch ? ver.toString() : "~>" ~ ver.toString(); + } catch (Exception e) { + notFound ~= dep; } } + if(notFound.length > 1){ throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound)); } @@ -727,7 +771,9 @@ throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound)); } - initPackage(path, depVers, type, format); + if (m_dryRun) return; + + initPackage(path, depVers, type, format, recipe_callback); //Act smug to the user. logInfo("Successfully created an empty project in '%s'.", path.toNativeString()); diff --git a/source/dub/init.d b/source/dub/init.d index f710d56..7d8d98b 100644 --- a/source/dub/init.d +++ b/source/dub/init.d @@ -10,6 +10,8 @@ import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.core.log; import dub.package_ : PackageFormat, packageInfoFiles, defaultPackageFilename; +import dub.recipe.packagerecipe; +import dub.dependency; import std.datetime; import std.exception; @@ -18,13 +20,27 @@ import std.process; import std.string; - -void initPackage(Path root_path, string[string] deps, string type, PackageFormat format) +void initPackage(Path root_path, string[string] deps, string type, PackageFormat format, scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null) { + import std.conv : to; + import dub.recipe.io : writePackageRecipe; + void enforceDoesNotExist(string filename) { enforce(!existsFile(root_path ~ filename), "The target directory already contains a '"~filename~"' file. Aborting."); } + string username = getUserName(); + + PackageRecipe p; + p.name = root_path.head.toString().toLower(); + p.authors ~= username; + p.license = "proprietary"; + p.copyright = .format("Copyright © %s, %s", Clock.currTime().year, username); + foreach (pack, v; deps) { + import std.ascii : isDigit; + p.buildSettings.dependencies[pack] = Dependency(v); + } + //Check to see if a target directory needs to be created if( !root_path.empty ){ if( !existsFile(root_path) ) @@ -41,16 +57,19 @@ switch (type) { default: throw new Exception("Unknown package init type: "~type); - case "minimal": initMinimalPackage(root_path, deps, format); break; - case "vibe.d": initVibeDPackage(root_path, deps, format); break; - case "deimos": initDeimosPackage(root_path, deps, format); break; + case "minimal": initMinimalPackage(root_path, p); break; + case "vibe.d": initVibeDPackage(root_path, p); break; + case "deimos": initDeimosPackage(root_path, p); break; } + + if (recipe_callback) recipe_callback(p, format); + writePackageRecipe(root_path ~ ("dub."~format.to!string), p); writeGitignore(root_path); } -void initMinimalPackage(Path root_path, string[string] deps, PackageFormat format) +private void initMinimalPackage(Path root_path, ref PackageRecipe p) { - writePackageDescription(format, root_path, "A minimal D application.", deps); + p.description = "A minimal D application."; createDirectory(root_path ~ "source"); write((root_path ~ "source/app.d").toNativeString(), q{import std.stdio; @@ -62,13 +81,13 @@ }); } -void initVibeDPackage(Path root_path, string[string] deps, PackageFormat format) +private void initVibeDPackage(Path root_path, ref PackageRecipe p) { - if("vibe-d" !in deps) - deps["vibe-d"] = "~>0.7.23"; + if("vibe-d" !in p.dependencies) + p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.7.26"); + p.description = "A simple vibe.d server application."; + p.buildSettings.versions[""] ~= "VibeDefaultMain"; - writePackageDescription(format, root_path, "A simple vibe.d server application.", - deps, ["versions": ["VibeDefaultMain"]]); createDirectory(root_path ~ "source"); createDirectory(root_path ~ "views"); createDirectory(root_path ~ "public"); @@ -92,70 +111,18 @@ }); } -void initDeimosPackage(Path root_path, string[string] deps, PackageFormat format) +private void initDeimosPackage(Path root_path, ref PackageRecipe p) { + import dub.compilers.buildsettings : TargetType; + auto name = root_path.head.toString().toLower(); - writePackageDescription(format, root_path, "Deimos Bindings for "~name~".", - deps, ["importPaths": ["."]], ["targetType": "sourceLibrary"]); + p.description = format("Deimos Bindings for "~p.name~"."); + p.buildSettings.importPaths[""] ~= "."; + p.buildSettings.targetType = TargetType.sourceLibrary; createDirectory(root_path ~ "C"); createDirectory(root_path ~ "deimos"); } -void writePackageDescription(PackageFormat format, Path root_path, string description, string[string] dependencies = null, string[][string] array_fields = null, string[string] string_fields = null) -{ - final switch (format) { - case PackageFormat.json: - foreach (f, v; array_fields) string_fields[f] = .format("[%(%s, %)]", v); - writePackageJSON(root_path, description, dependencies, string_fields); - break; - case PackageFormat.sdl: - foreach (f, v; array_fields) string_fields[f] = .format("%(%s %)", v); - writePackageSDL(root_path, description, dependencies, string_fields); - break; - } -} - -private void writePackageJSON(Path root_path, string description, string[string] dependencies = null, string[string] raw_fields = null) -{ - import std.algorithm : map; - - assert(!root_path.empty); - - auto username = getUserName(); - auto fil = openFile(root_path ~ "dub.json", FileMode.append); - scope(exit) fil.close(); - - fil.formattedWrite("{\n\t\"name\": \"%s\",\n", root_path.head.toString().toLower()); - fil.formattedWrite("\t\"description\": \"%s\",\n", description); - fil.formattedWrite("\t\"copyright\": \"Copyright © %s, %s\",\n", Clock.currTime().year, username); - fil.formattedWrite("\t\"authors\": [\"%s\"],\n", username); - fil.formattedWrite("\t\"dependencies\": {"); - fil.formattedWrite("%(\n\t\t%s: %s,%)", dependencies); - fil.formattedWrite("\n\t}"); - fil.formattedWrite("%-(,\n\t\"%s\": %s%)", raw_fields); - fil.write("\n}\n"); -} - -private void writePackageSDL(Path root_path, string description, string[string] dependencies = null, string[string] raw_fields = null) -{ - import std.algorithm : map; - - assert(!root_path.empty); - - auto username = getUserName(); - auto fil = openFile(root_path ~ "dub.sdl", FileMode.append); - scope(exit) fil.close(); - - fil.formattedWrite("name \"%s\"\n", root_path.head.toString().toLower()); - fil.formattedWrite("description \"%s\"\n", description); - fil.formattedWrite("copyright \"Copyright © %s, %s\"\n", Clock.currTime().year, username); - fil.formattedWrite("authors \"%s\"\n", username); - foreach (d, v; dependencies) - fil.formattedWrite("dependency \"%s\" version=\"%s\"\n", d, v); - foreach (f, v; raw_fields) - fil.formattedWrite("%s %s\n", f, v); -} - void writeGitignore(Path root_path) { write((root_path ~ ".gitignore").toNativeString(), diff --git a/test/0-init-fail-json.sh b/test/0-init-fail-json.sh index 1a00eb8..63ebb5a 100755 --- a/test/0-init-fail-json.sh +++ b/test/0-init-fail-json.sh @@ -3,7 +3,7 @@ packname="0-init-fail-pack" deps="logger PACKAGE_DONT_EXIST" # would be very unlucky if it does exist... -$DUB init $packname $deps -f json +$DUB init -n $packname $deps -f json function cleanup { rm -rf $packname diff --git a/test/0-init-fail.sh b/test/0-init-fail.sh index f8cc21f..db594b2 100755 --- a/test/0-init-fail.sh +++ b/test/0-init-fail.sh @@ -3,7 +3,7 @@ packname="0-init-fail-pack" deps="logger PACKAGE_DONT_EXIST" # would be very unlucky if it does exist... -$DUB init $packname $deps +$DUB init -n $packname $deps function cleanup { rm -rf $packname diff --git a/test/0-init-interactive.dub.sdl b/test/0-init-interactive.dub.sdl new file mode 100644 index 0000000..3eaf63c --- /dev/null +++ b/test/0-init-interactive.dub.sdl @@ -0,0 +1,5 @@ +name "test" +description "desc" +authors "author" +copyright "copy" +license "gpl" diff --git a/test/0-init-interactive.sh b/test/0-init-interactive.sh new file mode 100755 index 0000000..65065f3 --- /dev/null +++ b/test/0-init-interactive.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +packname="0-init-interactive" + +echo -e "sdl\ntest\ndesc\nauthor\ngpl\ncopy\n\n" | $DUB init $packname + +function cleanup { + rm -rf $packname +} + +if [ ! -e $packname/dub.sdl ]; then # it failed + echo "No dub.sdl file has been generated." + cleanup + exit 1 +fi + +if ! diff $packname/dub.sdl "$CURR_DIR"/0-init-interactive.dub.sdl; then + echo "Contents of generated dub.sdl not as expected." + cleanup + exit 1 +fi + +cleanup +exit 0 diff --git a/test/0-init-multi-json.sh b/test/0-init-multi-json.sh index bc16fd8..8e0a441 100755 --- a/test/0-init-multi-json.sh +++ b/test/0-init-multi-json.sh @@ -4,7 +4,7 @@ deps="openssl logger" type="vibe.d" -$DUB init $packname $deps --type=$type -f json +$DUB init -n $packname $deps --type=$type -f json function cleanup { rm -rf $packname diff --git a/test/0-init-multi.sh b/test/0-init-multi.sh index 89d50b7..5f74a3d 100755 --- a/test/0-init-multi.sh +++ b/test/0-init-multi.sh @@ -4,7 +4,7 @@ deps="openssl logger" type="vibe.d" -$DUB init $packname $deps --type=$type --format sdl +$DUB init -n $packname $deps --type=$type --format sdl function cleanup { rm -rf $packname diff --git a/test/0-init-simple-json.sh b/test/0-init-simple-json.sh index 80a07ff..a18bd3d 100755 --- a/test/0-init-simple-json.sh +++ b/test/0-init-simple-json.sh @@ -2,7 +2,7 @@ packname="0-init-simple-pack" -$DUB init $packname -f json +$DUB init -n $packname -f json function cleanup { rm -rf $packname diff --git a/test/0-init-simple.sh b/test/0-init-simple.sh index 900bddc..6b25f5a 100755 --- a/test/0-init-simple.sh +++ b/test/0-init-simple.sh @@ -2,7 +2,7 @@ packname="0-init-simple-pack" -$DUB init $packname --format sdl +$DUB init -n $packname --format sdl function cleanup { rm -rf $packname diff --git a/test/issue346-redundant-flags.sh b/test/issue346-redundant-flags.sh index 3ee9ade..fb88526 100755 --- a/test/issue346-redundant-flags.sh +++ b/test/issue346-redundant-flags.sh @@ -1,4 +1,4 @@ #!/bin/sh cd ${CURR_DIR}/issue346-redundant-flags -${DUB} build --bare --force --compiler=${DC} -a x86 main | grep -e "-m32 -m32" 2>&1 && exit 1 || exit 0 +${DUB} build --bare --force --compiler=${DC} -a x86_64 -v main 2>&1 | grep -e "-m64 -m64" -c && exit 1 || exit 0