- /**
- Package recipe reading/writing facilities.
-
- Copyright: © 2015-2016, Sönke Ludwig
- License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
- Authors: Sönke Ludwig
- */
- module dub.recipe.io;
-
- import dub.recipe.packagerecipe;
- import dub.internal.logging;
- import dub.internal.vibecompat.core.file;
- import dub.internal.vibecompat.inet.path;
- import dub.internal.configy.Read;
-
- /** Reads a package recipe from a file.
-
- The file format (JSON/SDLang) will be determined from the file extension.
-
- Params:
- filename = NativePath of the package recipe file
- parent_name = Optional name of the parent package (if this is a sub package)
- mode = Whether to issue errors, warning, or ignore unknown keys in dub.json
-
- Returns: Returns the package recipe contents
- Throws: Throws an exception if an I/O or syntax error occurs
- */
- PackageRecipe readPackageRecipe(
- string filename, string parent_name = null, StrictMode mode = StrictMode.Ignore)
- {
- return readPackageRecipe(NativePath(filename), parent_name, mode);
- }
-
- /// ditto
- PackageRecipe readPackageRecipe(
- NativePath filename, string parent_name = null, StrictMode mode = StrictMode.Ignore)
- {
- import dub.internal.utils : stripUTF8Bom;
-
- string text = stripUTF8Bom(cast(string)readFile(filename));
- return parsePackageRecipe(text, filename.toNativeString(), parent_name, null, mode);
- }
-
- /** Parses an in-memory package recipe.
-
- The file format (JSON/SDLang) will be determined from the file extension.
-
- Params:
- contents = The contents of the recipe file
- filename = Name associated with the package recipe - this is only used
- to determine the file format from the file extension
- parent_name = Optional name of the parent package (if this is a sub
- package)
- default_package_name = Optional default package name (if no package name
- is found in the recipe this value will be used)
- mode = Whether to issue errors, warning, or ignore unknown keys in dub.json
-
- Returns: Returns the package recipe contents
- Throws: Throws an exception if an I/O or syntax error occurs
- */
- PackageRecipe parsePackageRecipe(string contents, string filename, string parent_name = null,
- string default_package_name = null, StrictMode mode = StrictMode.Ignore)
- {
- import std.algorithm : endsWith;
- import dub.compilers.buildsettings : TargetType;
- import dub.internal.vibecompat.data.json;
- import dub.recipe.json : parseJson;
- import dub.recipe.sdl : parseSDL;
-
- PackageRecipe ret;
-
- ret.name = default_package_name;
-
- if (filename.endsWith(".json"))
- {
- try {
- ret = parseConfigString!PackageRecipe(contents, filename, mode);
- fixDependenciesNames(ret.name, ret);
- } catch (ConfigException exc) {
- logWarn("Your `dub.json` file use non-conventional features that are deprecated");
- logWarn("Please adjust your `dub.json` file as those warnings will turn into errors in dub v1.40.0");
- logWarn("Error was: %s", exc);
- // Fallback to JSON parser
- ret = PackageRecipe.init;
- parseJson(ret, parseJsonString(contents, filename), parent_name);
- } catch (Exception exc) {
- logWarn("Your `dub.json` file use non-conventional features that are deprecated");
- logWarn("This is most likely due to duplicated keys.");
- logWarn("Please adjust your `dub.json` file as those warnings will turn into errors in dub v1.40.0");
- logWarn("Error was: %s", exc);
- // Fallback to JSON parser
- ret = PackageRecipe.init;
- parseJson(ret, parseJsonString(contents, filename), parent_name);
- }
- // `debug = ConfigFillerDebug` also enables verbose parser output
- debug (ConfigFillerDebug)
- {
- import std.stdio;
-
- PackageRecipe jsonret;
- parseJson(jsonret, parseJsonString(contents, filename), parent_name);
- if (ret != jsonret)
- {
- writeln("Content of JSON and YAML parsing differ for file: ", filename);
- writeln("-------------------------------------------------------------------");
- writeln("JSON (excepted): ", jsonret);
- writeln("-------------------------------------------------------------------");
- writeln("YAML (actual ): ", ret);
- writeln("========================================");
- ret = jsonret;
- }
- }
- }
- else if (filename.endsWith(".sdl")) parseSDL(ret, contents, parent_name, filename);
- else assert(false, "readPackageRecipe called with filename with unknown extension: "~filename);
-
- // Fix for issue #711: `targetType` should be inherited, or default to library
- static void sanitizeTargetType(ref PackageRecipe r) {
- TargetType defaultTT = (r.buildSettings.targetType == TargetType.autodetect) ?
- TargetType.library : r.buildSettings.targetType;
- foreach (ref conf; r.configurations)
- if (conf.buildSettings.targetType == TargetType.autodetect)
- conf.buildSettings.targetType = defaultTT;
-
- // recurse into sub packages
- foreach (ref subPackage; r.subPackages)
- sanitizeTargetType(subPackage.recipe);
- }
-
- sanitizeTargetType(ret);
-
- return ret;
- }
-
-
- unittest { // issue #711 - configuration default target type not correct for SDL
- import dub.compilers.buildsettings : TargetType;
- auto inputs = [
- "dub.sdl": "name \"test\"\nconfiguration \"a\" {\n}",
- "dub.json": "{\"name\": \"test\", \"configurations\": [{\"name\": \"a\"}]}"
- ];
- foreach (file, content; inputs) {
- auto pr = parsePackageRecipe(content, file);
- assert(pr.name == "test");
- assert(pr.configurations.length == 1);
- assert(pr.configurations[0].name == "a");
- assert(pr.configurations[0].buildSettings.targetType == TargetType.library);
- }
- }
-
- unittest { // issue #711 - configuration default target type not correct for SDL
- import dub.compilers.buildsettings : TargetType;
- auto inputs = [
- "dub.sdl": "name \"test\"\ntargetType \"autodetect\"\nconfiguration \"a\" {\n}",
- "dub.json": "{\"name\": \"test\", \"targetType\": \"autodetect\", \"configurations\": [{\"name\": \"a\"}]}"
- ];
- foreach (file, content; inputs) {
- auto pr = parsePackageRecipe(content, file);
- assert(pr.name == "test");
- assert(pr.configurations.length == 1);
- assert(pr.configurations[0].name == "a");
- assert(pr.configurations[0].buildSettings.targetType == TargetType.library);
- }
- }
-
- unittest { // issue #711 - configuration default target type not correct for SDL
- import dub.compilers.buildsettings : TargetType;
- auto inputs = [
- "dub.sdl": "name \"test\"\ntargetType \"executable\"\nconfiguration \"a\" {\n}",
- "dub.json": "{\"name\": \"test\", \"targetType\": \"executable\", \"configurations\": [{\"name\": \"a\"}]}"
- ];
- foreach (file, content; inputs) {
- auto pr = parsePackageRecipe(content, file);
- assert(pr.name == "test");
- assert(pr.configurations.length == 1);
- assert(pr.configurations[0].name == "a");
- assert(pr.configurations[0].buildSettings.targetType == TargetType.executable);
- }
- }
-
- unittest { // make sure targetType of sub packages are sanitized too
- import dub.compilers.buildsettings : TargetType;
- auto inputs = [
- "dub.sdl": "name \"test\"\nsubPackage {\nname \"sub\"\ntargetType \"sourceLibrary\"\nconfiguration \"a\" {\n}\n}",
- "dub.json": "{\"name\": \"test\", \"subPackages\": [ { \"name\": \"sub\", \"targetType\": \"sourceLibrary\", \"configurations\": [{\"name\": \"a\"}] } ] }"
- ];
- foreach (file, content; inputs) {
- auto pr = parsePackageRecipe(content, file);
- assert(pr.name == "test");
- const spr = pr.subPackages[0].recipe;
- assert(spr.name == "sub");
- assert(spr.configurations.length == 1);
- assert(spr.configurations[0].name == "a");
- assert(spr.configurations[0].buildSettings.targetType == TargetType.sourceLibrary);
- }
- }
-
-
- /** Writes the textual representation of a package recipe to a file.
-
- Note that the file extension must be either "json" or "sdl".
- */
- void writePackageRecipe(string filename, const scope ref PackageRecipe recipe)
- {
- writePackageRecipe(NativePath(filename), recipe);
- }
-
- /// ditto
- void writePackageRecipe(NativePath filename, const scope ref PackageRecipe recipe)
- {
- import std.array;
- auto app = appender!string();
- serializePackageRecipe(app, recipe, filename.toNativeString());
- writeFile(filename, app.data);
- }
-
- /** Converts a package recipe to its textual representation.
-
- The extension of the supplied `filename` must be either "json" or "sdl".
- The output format is chosen accordingly.
- */
- void serializePackageRecipe(R)(ref R dst, const scope ref PackageRecipe recipe, string filename)
- {
- import std.algorithm : endsWith;
- import dub.internal.vibecompat.data.json : writeJsonString;
- import dub.recipe.json : toJson;
- import dub.recipe.sdl : toSDL;
-
- if (filename.endsWith(".json"))
- dst.writeJsonString!(R, true)(toJson(recipe));
- else if (filename.endsWith(".sdl"))
- toSDL(recipe).toSDLDocument(dst);
- else assert(false, "writePackageRecipe called with filename with unknown extension: "~filename);
- }
-
- unittest {
- import std.format;
- import dub.dependency;
- import dub.internal.utils : deepCompare;
-
- static void success (string source, in PackageRecipe expected, size_t line = __LINE__) {
- const result = parseConfigString!PackageRecipe(source, "dub.json");
- deepCompare(result, expected, __FILE__, line);
- }
-
- static void error (string source, string expected, size_t line = __LINE__) {
- try
- {
- auto result = parseConfigString!PackageRecipe(source, "dub.json");
- assert(0,
- format("[%s:%d] Exception should have been thrown but wasn't: %s",
- __FILE__, line, result));
- }
- catch (Exception exc)
- assert(exc.toString() == expected,
- format("[%s:%s] result != expected: '%s' != '%s'",
- __FILE__, line, exc.toString(), expected));
- }
-
- alias YAMLDep = typeof(BuildSettingsTemplate.dependencies[string.init]);
- const PackageRecipe expected1 =
- {
- name: "foo",
- buildSettings: {
- dependencies: RecipeDependencyAA([
- "repo": YAMLDep(Dependency(Repository(
- "git+https://github.com/dlang/dmd",
- "09d04945bdbc0cba36f7bb1e19d5bd009d4b0ff2",
- ))),
- "path": YAMLDep(Dependency(NativePath("/foo/bar/jar/"))),
- "version": YAMLDep(Dependency(VersionRange.fromString("~>1.0"))),
- "version2": YAMLDep(Dependency(Version("4.2.0"))),
- ])},
- };
- success(
- `{ "name": "foo", "dependencies": {
- "repo": { "repository": "git+https://github.com/dlang/dmd",
- "version": "09d04945bdbc0cba36f7bb1e19d5bd009d4b0ff2" },
- "path": { "path": "/foo/bar/jar/" },
- "version": { "version": "~>1.0" },
- "version2": "4.2.0"
- }}`, expected1);
-
-
- error(`{ "name": "bar", "dependencies": {"bad": { "repository": "git+https://github.com/dlang/dmd" }}}`,
- "dub.json(0:41): dependencies[bad]: Need to provide a commit hash in 'version' field with 'repository' dependency");
- }