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 p.license = "proprietary"; 60 foreach (pack, v; deps) { 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 ensureDirectory(root_path); 67 } 68 69 //Make sure we do not overwrite anything accidentally 70 foreach (fil; packageInfoFiles) 71 enforceDoesNotExist(fil.filename); 72 73 auto files = ["source/", "views/", "public/", "dub.json"]; 74 foreach (fil; files) 75 enforceDoesNotExist(fil); 76 77 void processRecipe() 78 { 79 if (recipe_callback) 80 recipe_callback(p, format); 81 } 82 83 switch (type) { 84 default: break; 85 case "minimal": initMinimalPackage(root_path, p, &processRecipe); break; 86 case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break; 87 case "deimos": initDeimosPackage(root_path, p, &processRecipe); break; 88 } 89 90 writePackageRecipe(root_path ~ ("dub."~format.to!string), p); 91 writeGitignore(root_path, p.name); 92 } 93 94 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat); 95 96 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 97 { 98 p.description = "A minimal D application."; 99 pre_write_callback(); 100 101 ensureDirectory(root_path ~ "source"); 102 write((root_path ~ "source/app.d").toNativeString(), 103 q{import std.stdio; 104 105 void main() 106 { 107 writeln("Edit source/app.d to start your project."); 108 } 109 }); 110 } 111 112 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 113 { 114 if ("vibe-d" !in p.buildSettings.dependencies) 115 p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9"); 116 p.description = "A simple vibe.d server application."; 117 pre_write_callback(); 118 119 ensureDirectory(root_path ~ "source"); 120 ensureDirectory(root_path ~ "views"); 121 ensureDirectory(root_path ~ "public"); 122 write((root_path ~ "source/app.d").toNativeString(), 123 q{import vibe.vibe; 124 125 void main() 126 { 127 auto settings = new HTTPServerSettings; 128 settings.port = 8080; 129 settings.bindAddresses = ["::1", "127.0.0.1"]; 130 auto listener = listenHTTP(settings, &hello); 131 scope (exit) 132 { 133 listener.stopListening(); 134 } 135 136 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 137 runApplication(); 138 } 139 140 void hello(HTTPServerRequest req, HTTPServerResponse res) 141 { 142 res.writeBody("Hello, World!"); 143 } 144 }); 145 } 146 147 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 148 { 149 import dub.compilers.buildsettings : TargetType; 150 151 p.description = format("Deimos Bindings for "~p.name~"."); 152 p.buildSettings.importPaths[""] ~= "."; 153 p.buildSettings.targetType = TargetType.sourceLibrary; 154 pre_write_callback(); 155 156 ensureDirectory(root_path ~ "C"); 157 ensureDirectory(root_path ~ "deimos"); 158 } 159 160 /** 161 * Write the `.gitignore` file to the directory, if it does not already exists 162 * 163 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 164 * most users. However, this file is not mandatory for `dub` to do its job, 165 * so we do not depend on the content. 166 * One important use case we need to support is people running `dub init` on 167 * a GitHub-initialized repository. Those might already contain a `.gitignore` 168 * (and a README and a LICENSE), thus we should not bail out if the file already 169 * exists, just ignore it. 170 * 171 * Params: 172 * root_path = The path to the directory hosting the project 173 * pkg_name = Name of the package, to generate a list of binaries to ignore 174 */ 175 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 176 { 177 auto full_path = (root_path ~ ".gitignore").toNativeString(); 178 179 if (existsFile(full_path)) 180 return; 181 182 write(full_path, 183 q"{.dub 184 docs.json 185 __dummy.html 186 docs/ 187 /%1$s 188 %1$s.so 189 %1$s.dylib 190 %1$s.dll 191 %1$s.a 192 %1$s.lib 193 %1$s-test-* 194 *.exe 195 *.pdb 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 }