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