1 /** 2 Package skeleton initialization code. 3 4 Copyright: © 2013-2016 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.init; 9 10 import dub.internal.vibecompat.core.file; 11 import dub.internal.logging; 12 import dub.package_ : PackageFormat, packageInfoFiles, defaultPackageFilename; 13 import dub.recipe.packagerecipe; 14 import dub.dependency; 15 16 import std.exception; 17 import std.file; 18 import std.format; 19 import std.process : environment; 20 import std.string; 21 22 23 /** Initializes a new package in the given directory. 24 25 The given `root_path` will be checked for any of the files that will be 26 created by this function. If any exist, an exception will be thrown before 27 altering the directory. 28 29 Params: 30 root_path = Directory in which to create the new package. If the 31 directory doesn't exist, a new one will be created. 32 deps = A set of extra dependencies to add to the package recipe. The 33 associative array is expected to map from package name to package 34 version. 35 type = The type of package skeleton to create. Can currently be 36 "minimal", "vibe.d" or "deimos" 37 format = Format in which the recipe will be written (SDL / JSON) 38 recipe_callback = Optional callback that can be used to customize the 39 package recipe and the file format used to store it prior to 40 writing it to disk. 41 */ 42 void initPackage(NativePath root_path, VersionRange[string] deps, string type, 43 PackageFormat format, scope RecipeCallback recipe_callback = null) 44 { 45 import std.conv : to; 46 import dub.recipe.io : writePackageRecipe; 47 48 void enforceDoesNotExist(string filename) { 49 enforce(!existsFile(root_path ~ filename), 50 "The target directory already contains a '%s' %s. Aborting." 51 .format(filename, filename.isDir ? "directory" : "file")); 52 } 53 54 string username = getUserName(); 55 56 PackageRecipe p; 57 p.name = root_path.head.name.toLower(); 58 p.authors ~= username; 59 // Use proprietary as conservative default, so that we don't announce a more 60 // permissive license than actually chosen in case the dub.json wasn't updated. 61 p.license = "proprietary"; 62 foreach (pack, v; deps) { 63 p.buildSettings.dependencies[pack] = Dependency(v); 64 } 65 66 //Check to see if a target directory needs to be created 67 if (!root_path.empty) { 68 ensureDirectory(root_path); 69 } 70 71 //Make sure we do not overwrite anything accidentally 72 foreach (fil; packageInfoFiles) 73 enforceDoesNotExist(fil.filename); 74 75 auto files = ["source/", "views/", "public/", "dub.json"]; 76 foreach (fil; files) 77 enforceDoesNotExist(fil); 78 79 void processRecipe() 80 { 81 if (recipe_callback) 82 recipe_callback(p, format); 83 } 84 85 switch (type) { 86 default: break; 87 case "minimal": initMinimalPackage(root_path, p, &processRecipe); break; 88 case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break; 89 case "deimos": initDeimosPackage(root_path, p, &processRecipe); break; 90 } 91 92 writePackageRecipe(root_path ~ ("dub."~format.to!string), p); 93 writeGitignore(root_path, p.name); 94 } 95 96 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat); 97 98 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 99 { 100 p.description = "A minimal D application."; 101 pre_write_callback(); 102 103 ensureDirectory(root_path ~ "source"); 104 write((root_path ~ "source/app.d").toNativeString(), 105 q{import std.stdio; 106 107 void main() 108 { 109 writeln("Edit source/app.d to start your project."); 110 } 111 }); 112 } 113 114 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 115 { 116 if ("vibe-d" !in p.buildSettings.dependencies) 117 p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9"); 118 p.description = "A simple vibe.d server application."; 119 pre_write_callback(); 120 121 ensureDirectory(root_path ~ "source"); 122 ensureDirectory(root_path ~ "views"); 123 ensureDirectory(root_path ~ "public"); 124 write((root_path ~ "source/app.d").toNativeString(), 125 q{import vibe.vibe; 126 127 void main() 128 { 129 auto settings = new HTTPServerSettings; 130 settings.port = 8080; 131 settings.bindAddresses = ["::1", "127.0.0.1"]; 132 auto listener = listenHTTP(settings, &hello); 133 scope (exit) 134 { 135 listener.stopListening(); 136 } 137 138 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 139 runApplication(); 140 } 141 142 void hello(HTTPServerRequest req, HTTPServerResponse res) 143 { 144 res.writeBody("Hello, World!"); 145 } 146 }); 147 } 148 149 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 150 { 151 import dub.compilers.buildsettings : TargetType; 152 153 p.description = format("Deimos Bindings for "~p.name~"."); 154 p.buildSettings.importPaths[""] ~= "."; 155 p.buildSettings.targetType = TargetType.sourceLibrary; 156 pre_write_callback(); 157 158 ensureDirectory(root_path ~ "C"); 159 ensureDirectory(root_path ~ "deimos"); 160 } 161 162 /** 163 * Write the `.gitignore` file to the directory, if it does not already exists 164 * 165 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 166 * most users. However, this file is not mandatory for `dub` to do its job, 167 * so we do not depend on the content. 168 * One important use case we need to support is people running `dub init` on 169 * a GitHub-initialized repository. Those might already contain a `.gitignore` 170 * (and a README and a LICENSE), thus we should not bail out if the file already 171 * exists, just ignore it. 172 * 173 * Params: 174 * root_path = The path to the directory hosting the project 175 * pkg_name = Name of the package, to generate a list of binaries to ignore 176 */ 177 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 178 { 179 auto full_path = (root_path ~ ".gitignore").toNativeString(); 180 181 if (existsFile(full_path)) 182 return; 183 184 write(full_path, 185 q"{.dub 186 docs.json 187 __dummy.html 188 docs/ 189 /%1$s 190 %1$s.so 191 %1$s.dylib 192 %1$s.dll 193 %1$s.a 194 %1$s.lib 195 %1$s-test-* 196 *.exe 197 *.pdb 198 *.o 199 *.obj 200 *.lst 201 }".format(pkg_name)); 202 } 203 204 private string getUserName() 205 { 206 version (Windows) 207 return environment.get("USERNAME", "Peter Parker"); 208 else version (Posix) 209 { 210 import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen; 211 import std.algorithm : splitter; 212 213 // Bionic doesn't have pw_gecos on ARM 214 version(CRuntime_Bionic) {} else 215 if (auto pw = getpwuid(getuid)) 216 { 217 auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); 218 if (!uinfo.empty && uinfo.front.length) 219 return uinfo.front.idup; 220 } 221 return environment.get("USER", "Peter Parker"); 222 } 223 else 224 static assert(0); 225 }