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.vibecompat.core.log; 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; 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 recipe_callback = Optional callback that can be used to customize the 38 package recipe and the file format used to store it prior to 39 writing it to disk. 40 */ 41 void initPackage(NativePath root_path, string[string] deps, string type, 42 PackageFormat format, scope RecipeCallback recipe_callback = null) 43 { 44 import std.conv : to; 45 import dub.recipe.io : writePackageRecipe; 46 47 void enforceDoesNotExist(string filename) { 48 enforce(!existsFile(root_path ~ filename), 49 "The target directory already contains a '%s' %s. Aborting." 50 .format(filename, filename.isDir ? "directory" : "file")); 51 } 52 53 string username = getUserName(); 54 55 PackageRecipe p; 56 p.name = root_path.head.name.toLower(); 57 p.authors ~= username; 58 p.license = "proprietary"; 59 foreach (pack, v; deps) { 60 import std.ascii : isDigit; 61 p.buildSettings.dependencies[pack] = Dependency(v); 62 } 63 64 //Check to see if a target directory needs to be created 65 if (!root_path.empty) { 66 if (!existsFile(root_path)) 67 createDirectory(root_path); 68 } 69 70 //Make sure we do not overwrite anything accidentally 71 foreach (fil; packageInfoFiles) 72 enforceDoesNotExist(fil.filename); 73 74 auto files = ["source/", "views/", "public/", "dub.json"]; 75 foreach (fil; files) 76 enforceDoesNotExist(fil); 77 78 void processRecipe() 79 { 80 if (recipe_callback) 81 recipe_callback(p, format); 82 } 83 84 switch (type) { 85 default: break; 86 case "minimal": initMinimalPackage(root_path, p, &processRecipe); break; 87 case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break; 88 case "deimos": initDeimosPackage(root_path, p, &processRecipe); break; 89 } 90 91 writePackageRecipe(root_path ~ ("dub."~format.to!string), p); 92 writeGitignore(root_path, p.name); 93 } 94 95 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat); 96 97 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 98 { 99 p.description = "A minimal D application."; 100 pre_write_callback(); 101 102 createDirectory(root_path ~ "source"); 103 write((root_path ~ "source/app.d").toNativeString(), 104 q{import std.stdio; 105 106 void main() 107 { 108 writeln("Edit source/app.d to start your project."); 109 } 110 }); 111 } 112 113 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 114 { 115 if ("vibe-d" !in p.buildSettings.dependencies) 116 p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9"); 117 p.description = "A simple vibe.d server application."; 118 pre_write_callback(); 119 120 createDirectory(root_path ~ "source"); 121 createDirectory(root_path ~ "views"); 122 createDirectory(root_path ~ "public"); 123 write((root_path ~ "source/app.d").toNativeString(), 124 q{import vibe.vibe; 125 126 void main() 127 { 128 auto settings = new HTTPServerSettings; 129 settings.port = 8080; 130 settings.bindAddresses = ["::1", "127.0.0.1"]; 131 auto listener = listenHTTP(settings, &hello); 132 scope (exit) 133 { 134 listener.stopListening(); 135 } 136 137 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 138 runApplication(); 139 } 140 141 void hello(HTTPServerRequest req, HTTPServerResponse res) 142 { 143 res.writeBody("Hello, World!"); 144 } 145 }); 146 } 147 148 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 149 { 150 import dub.compilers.buildsettings : TargetType; 151 152 p.description = format("Deimos Bindings for "~p.name~"."); 153 p.buildSettings.importPaths[""] ~= "."; 154 p.buildSettings.targetType = TargetType.sourceLibrary; 155 pre_write_callback(); 156 157 createDirectory(root_path ~ "C"); 158 createDirectory(root_path ~ "deimos"); 159 } 160 161 /** 162 * Write the `.gitignore` file to the directory, if it does not already exists 163 * 164 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 165 * most users. However, this file is not mandatory for `dub` to do its job, 166 * so we do not depend on the content. 167 * One important use case we need to support is people running `dub init` on 168 * a Github-initialized repository. Those might already contain a `.gitignore` 169 * (and a README and a LICENSE), thus we should not bail out if the file already 170 * exists, just ignore it. 171 * 172 * Params: 173 * root_path = The path to the directory hosting the project 174 * pkg_name = Name of the package, to generate a list of binaries to ignore 175 */ 176 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 177 { 178 auto full_path = (root_path ~ ".gitignore").toNativeString(); 179 180 if (existsFile(full_path)) 181 return; 182 183 write(full_path, 184 q"{.dub 185 docs.json 186 __dummy.html 187 docs/ 188 /%1$s 189 %1$s.so 190 %1$s.dylib 191 %1$s.dll 192 %1$s.a 193 %1$s.lib 194 %1$s-test-* 195 *.exe 196 *.o 197 *.obj 198 *.lst 199 }".format(pkg_name)); 200 } 201 202 private string getUserName() 203 { 204 version (Windows) 205 return environment.get("USERNAME", "Peter Parker"); 206 else version (Posix) 207 { 208 import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc..string : strlen; 209 import std.algorithm : splitter; 210 211 // Bionic doesn't have pw_gecos on ARM 212 version(CRuntime_Bionic) {} else 213 if (auto pw = getpwuid(getuid)) 214 { 215 auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); 216 if (!uinfo.empty && uinfo.front.length) 217 return uinfo.front.idup; 218 } 219 return environment.get("USER", "Peter Parker"); 220 } 221 else 222 static assert(0); 223 }