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.toString().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.8.2"); 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 listenHTTP(settings, &hello); 132 133 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 134 runApplication(); 135 } 136 137 void hello(HTTPServerRequest req, HTTPServerResponse res) 138 { 139 res.writeBody("Hello, World!"); 140 } 141 }); 142 } 143 144 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 145 { 146 import dub.compilers.buildsettings : TargetType; 147 148 auto name = root_path.head.toString().toLower(); 149 p.description = format("Deimos Bindings for "~p.name~"."); 150 p.buildSettings.importPaths[""] ~= "."; 151 p.buildSettings.targetType = TargetType.sourceLibrary; 152 pre_write_callback(); 153 154 createDirectory(root_path ~ "C"); 155 createDirectory(root_path ~ "deimos"); 156 } 157 158 /** 159 * Write the `.gitignore` file to the directory, if it does not already exists 160 * 161 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 162 * most users. However, this file is not mandatory for `dub` to do its job, 163 * so we do not depend on the content. 164 * One important use case we need to support is people running `dub init` on 165 * a Github-initialized repository. Those might already contain a `.gitignore` 166 * (and a README and a LICENSE), thus we should not bail out if the file already 167 * exists, just ignore it. 168 * 169 * Params: 170 * root_path = The path to the directory hosting the project 171 * pkg_name = Name of the package, to generate a list of binaries to ignore 172 */ 173 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 174 { 175 auto full_path = (root_path ~ ".gitignore").toNativeString(); 176 177 if (existsFile(full_path)) 178 return; 179 180 write(full_path, 181 q"{.dub 182 docs.json 183 __dummy.html 184 docs/ 185 /%1$s 186 %1$s.so 187 %1$s.dylib 188 %1$s.dll 189 %1$s.a 190 %1$s.lib 191 %1$s-test-* 192 *.exe 193 *.o 194 *.obj 195 *.lst 196 }".format(pkg_name)); 197 } 198 199 private string getUserName() 200 { 201 version (Windows) 202 return environment.get("USERNAME", "Peter Parker"); 203 else version (Posix) 204 { 205 import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc..string : strlen; 206 import std.algorithm : splitter; 207 208 // Bionic doesn't have pw_gecos on ARM 209 version(CRuntime_Bionic) {} else 210 if (auto pw = getpwuid(getuid)) 211 { 212 auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); 213 if (!uinfo.empty && uinfo.front.length) 214 return uinfo.front.idup; 215 } 216 return environment.get("USER", "Peter Parker"); 217 } 218 else 219 static assert(0); 220 }