1 /** 2 Generator for project files 3 4 Copyright: © 2012-2013 Matthias Dondorff 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.generators.generator; 9 10 import dub.compilers.compiler; 11 import dub.generators.build; 12 import dub.generators.visuald; 13 import dub.internal.vibecompat.core.file; 14 import dub.internal.vibecompat.core.log; 15 import dub.internal.vibecompat.inet.path; 16 import dub.package_; 17 import dub.packagemanager; 18 import dub.project; 19 20 import std.array; 21 import std.exception; 22 import std.file; 23 import std..string; 24 25 26 /** 27 Common interface for project generators/builders. 28 */ 29 class ProjectGenerator 30 { 31 struct TargetInfo { 32 Package pack; 33 Package[] packages; 34 string config; 35 BuildSettings buildSettings; 36 string[] dependencies; 37 string[] linkDependencies; 38 } 39 40 protected { 41 Project m_project; 42 } 43 44 this(Project project) 45 { 46 m_project = project; 47 } 48 49 void generate(GeneratorSettings settings) 50 { 51 if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); 52 53 TargetInfo[string] targets; 54 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 55 56 string[] mainfiles; 57 collect(settings, m_project.mainPackage, targets, configs, mainfiles, null); 58 downwardsInheritSettings(m_project.mainPackage.name, targets, targets[m_project.mainPackage.name].buildSettings); 59 auto bs = &targets[m_project.mainPackage.name].buildSettings; 60 if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles); 61 62 generateTargets(settings, targets); 63 } 64 65 abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 66 67 private BuildSettings collect(GeneratorSettings settings, Package pack, ref TargetInfo[string] targets, in string[string] configs, ref string[] main_files, string bin_pack) 68 { 69 if (auto pt = pack.name in targets) return pt.buildSettings; 70 71 // determine the actual target type 72 auto shallowbs = pack.getBuildSettings(settings.platform, configs[pack.name]); 73 TargetType tt = shallowbs.targetType; 74 if (pack is m_project.mainPackage) { 75 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 76 } else { 77 if (tt == TargetType.autodetect || tt == TargetType.library) tt = settings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 78 else if (tt == TargetType.dynamicLibrary) { 79 logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); 80 tt = TargetType.staticLibrary; 81 } 82 } 83 shallowbs.targetType = tt; 84 bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; 85 86 // start to build up the build settings 87 BuildSettings buildsettings = settings.buildSettings.dup; 88 processVars(buildsettings, pack.path.toNativeString(), shallowbs, true); 89 buildsettings.addVersions("Have_" ~ stripDlangSpecialChars(pack.name)); 90 91 // remove any mainSourceFile from library builds 92 if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) { 93 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array; 94 main_files ~= buildsettings.mainSourceFile; 95 } 96 97 logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName); 98 if (generates_binary) 99 targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null); 100 101 foreach (depname, depspec; pack.dependencies) { 102 if (!pack.hasDependency(depname, configs[pack.name])) continue; 103 auto dep = m_project.getDependency(depname, depspec.optional); 104 if (!dep) continue; 105 106 auto depbs = collect(settings, dep, targets, configs, main_files, generates_binary ? pack.name : bin_pack); 107 108 if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) { 109 // add a reference to the target binary and remove all source files in the dependency build settings 110 depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array; 111 depbs.importFiles = null; 112 } 113 114 buildsettings.add(depbs); 115 116 if (generates_binary) { 117 auto pt = pack.name in targets; 118 assert(pt !is null); 119 if (auto pdt = depname in targets) { 120 pt.dependencies ~= depname; 121 pt.linkDependencies ~= depname; 122 if (depbs.targetType == TargetType.staticLibrary) 123 pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies; 124 } else pt.packages ~= dep; 125 } else targets[bin_pack].packages ~= dep; 126 } 127 128 if (generates_binary) { 129 // add build type settings and convert plain DFLAGS to build options 130 m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType); 131 settings.compiler.extractBuildOptions(buildsettings); 132 enforceBuildRequirements(buildsettings); 133 targets[pack.name].buildSettings = buildsettings.dup; 134 } 135 136 return buildsettings; 137 } 138 139 private void downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings) 140 { 141 auto ti = &targets[target]; 142 ti.buildSettings.addVersions(root_settings.versions); 143 ti.buildSettings.addDebugVersions(root_settings.debugVersions); 144 ti.buildSettings.addOptions(root_settings.options); 145 ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths); 146 147 foreach (d; ti.dependencies) 148 downwardsInheritSettings(d, targets, root_settings); 149 } 150 } 151 152 153 struct GeneratorSettings { 154 BuildPlatform platform; 155 Compiler compiler; 156 string config; 157 string buildType; 158 BuildSettings buildSettings; 159 160 bool combined; // compile all in one go instead of each dependency separately 161 162 // only used for generator "build" 163 bool run, force, direct, clean, rdmd; 164 string[] runArgs; 165 } 166 167 168 /** 169 Creates a project generator of the given type for the specified project. 170 */ 171 ProjectGenerator createProjectGenerator(string generator_type, Project app, PackageManager mgr) 172 { 173 assert(app !is null && mgr !is null, "Project and package manager needed to create a generator."); 174 175 generator_type = generator_type.toLower(); 176 switch(generator_type) { 177 default: 178 throw new Exception("Unknown project generator: "~generator_type); 179 case "build": 180 logDebug("Creating build generator."); 181 return new BuildGenerator(app, mgr); 182 case "mono-d": 183 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 184 case "visuald": 185 logDebug("Creating VisualD generator."); 186 return new VisualDGenerator(app, mgr); 187 } 188 } 189 190 191 /** 192 Runs pre-build commands and performs other required setup before project files are generated. 193 */ 194 void prepareGeneration(in BuildSettings buildsettings) 195 { 196 if( buildsettings.preGenerateCommands.length ){ 197 logInfo("Running pre-generate commands..."); 198 runBuildCommands(buildsettings.preGenerateCommands, buildsettings); 199 } 200 } 201 202 /** 203 Runs post-build commands and copies required files to the binary directory. 204 */ 205 void finalizeGeneration(in BuildSettings buildsettings, bool generate_binary) 206 { 207 if (buildsettings.postGenerateCommands.length) { 208 logInfo("Running post-generate commands..."); 209 runBuildCommands(buildsettings.postGenerateCommands, buildsettings); 210 } 211 212 if (generate_binary) { 213 if (!exists(buildsettings.targetPath)) 214 mkdirRecurse(buildsettings.targetPath); 215 216 if (buildsettings.copyFiles.length) { 217 logInfo("Copying files..."); 218 foreach (f; buildsettings.copyFiles) { 219 auto src = Path(f); 220 auto dst = Path(buildsettings.targetPath) ~ Path(f).head; 221 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 222 try { 223 copyFile(src, dst, true); 224 } catch logWarn("Failed to copy to %s", dst.toNativeString()); 225 } 226 } 227 } 228 } 229 230 void runBuildCommands(in string[] commands, in BuildSettings build_settings) 231 { 232 import std.process; 233 import dub.internal.utils; 234 235 string[string] env = environment.toAA(); 236 // TODO: do more elaborate things here 237 // TODO: escape/quote individual items appropriately 238 env["DFLAGS"] = join(cast(string[])build_settings.dflags, " "); 239 env["LFLAGS"] = join(cast(string[])build_settings.lflags," "); 240 env["VERSIONS"] = join(cast(string[])build_settings.versions," "); 241 env["LIBS"] = join(cast(string[])build_settings.libs," "); 242 env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," "); 243 env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," "); 244 runCommands(commands, env); 245 }