-
-
Notifications
You must be signed in to change notification settings - Fork 232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add flag --submodules to add git submodules in the project folder as available packages. #1780
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Support for git submodules as packages | ||
|
||
Dub now supports the flag `--submodules`, which will scan the root folder for git submodules and add them as | ||
available packages. | ||
The git tag on the repository defines the version of the package. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// functionality to supply packages from git submodules | ||
module dub.git; | ||
|
||
import dub.dependency; | ||
import dub.internal.vibecompat.core.file; | ||
import dub.package_; | ||
import dub.packagemanager; | ||
|
||
import std.algorithm; | ||
import std.ascii : newline; | ||
import std.exception : enforce; | ||
import std.range; | ||
import std.string; | ||
|
||
/** Adds the git submodules checked out in the root path as direct packages. | ||
Package version is derived from the submodule's tag by `getOrLoadPackage`. | ||
|
||
Params: | ||
packageManager = Package manager to track the added packages. | ||
rootPath = the root path of the git repository to check for submodules. | ||
*/ | ||
public void addGitSubmodules(PackageManager packageManager, NativePath rootPath) { | ||
import std.process : execute; | ||
|
||
auto rootScmPath = rootPath ~ ".git"; | ||
const submoduleInfo = execute([ | ||
"git", | ||
"-C", rootPath.toNativeString, | ||
"--git-dir=" ~ (rootScmPath.relativeTo(rootPath)).toNativeString, | ||
"submodule", "status"]); | ||
|
||
enforce(submoduleInfo.status == 0, | ||
format("git submodule status exited with error code %s: %s", submoduleInfo.status, submoduleInfo.output)); | ||
|
||
foreach (line; submoduleInfo.output.lines) { | ||
const parts = line.split(" ").map!strip.filter!(a => !a.empty).array; | ||
const subPath = rootPath ~ parts[1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we do sth. less fragile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also not enthused about it, but I'm not sure how to improve on it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bash code to enumerate registered active submodules: git config -lz | \
while IFS=$'\n' read -r -d '' name value
do
if [[ "$name" =~ ^submodule\.(.*)\.url$ || ( "$name" =~ ^submodule\.(.*)\.active$ && "$value" == true ) ]]
then
printf '%s\0' "${BASH_REMATCH[1]}"
fi
done | \
sort -uz | \
mapfile -d '' -t list See https://git-scm.com/docs/gitsubmodules#_active_submodules for reference. To get the submodule git dir (of an absorbed module): git rev-parse --git-path modules/"$m" where To get the submodule work dir: git -C "$md" rev-parse --show-toplevel where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this purpose probably we want to look at unregistered submodules too (i.e. submodule entries in the tree). In which case, the equivalent of: git ls-tree -r --full-tree HEAD -z | grep -z '^160000 ' | cut -z -d $'\t' -f 2 | xargs -0 printf '%s\n' would be more appropriate. Then, just check for a |
||
const packageFile = Package.findPackageFile(subPath); | ||
|
||
if (packageFile != NativePath.init) { | ||
const scmPath = rootPath ~ NativePath(".git/modules/" ~ parts[1]); | ||
packageManager.getOrLoadPackage(subPath, packageFile, false, scmPath); | ||
} | ||
} | ||
} | ||
|
||
private alias lines = text => text.split(newline).map!strip.filter!(a => !a.empty); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,20 +75,24 @@ class Package { | |
root = The directory in which the package resides (if any). | ||
parent = Reference to the parent package, if the new package is a | ||
sub package. | ||
scm_path = The directory in which the VCS (Git) stores its state. | ||
Different than root/.git for submodules. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems unnecessary |
||
version_override = Optional version to associate to the package | ||
instead of the one declared in the package recipe, or the one | ||
determined by invoking the VCS (GIT currently). | ||
determined by invoking the VCS (Git currently). | ||
*/ | ||
this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") | ||
this(Json json_recipe, NativePath root = NativePath(), Package parent = null, | ||
NativePath scm_path = NativePath(), string version_override = "") | ||
{ | ||
import dub.recipe.json; | ||
|
||
PackageRecipe recipe; | ||
parseJson(recipe, json_recipe, parent ? parent.name : null); | ||
this(recipe, root, parent, version_override); | ||
this(recipe, root, parent, version_override, scm_path); | ||
} | ||
/// ditto | ||
this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") | ||
this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, | ||
string version_override = "", NativePath scm_path = NativePath()) | ||
Comment on lines
-82
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
{ | ||
// save the original recipe | ||
m_rawRecipe = recipe.clone; | ||
|
@@ -98,15 +102,15 @@ class Package { | |
|
||
// try to run git to determine the version of the package if no explicit version was given | ||
if (recipe.version_.length == 0 && !parent) { | ||
try recipe.version_ = determineVersionFromSCM(root); | ||
try recipe.version_ = determineVersionFromSCM(root, scm_path); | ||
Comment on lines
-101
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); | ||
|
||
if (recipe.version_.length == 0) { | ||
logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString()); | ||
logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, root.toNativeString()); | ||
// TODO: Assume unknown version here? | ||
// recipe.version_ = Version.unknown.toString(); | ||
recipe.version_ = Version.masterBranch.toString(); | ||
} else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_); | ||
} else logDiagnostic("Determined package version using Git: %s %s", recipe.name, recipe.version_); | ||
} | ||
|
||
m_parentPackage = parent; | ||
|
@@ -146,11 +150,15 @@ class Package { | |
empty, the `root` directory will be searched for a recipe file. | ||
parent = Reference to the parent package, if the new package is a | ||
sub package. | ||
scm_path = The directory in which the VCS (Git) stores its state. | ||
Different than root/.git for submodules! | ||
Comment on lines
+153
to
+154
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
version_override = Optional version to associate to the package | ||
instead of the one declared in the package recipe, or the one | ||
determined by invoking the VCS (GIT currently). | ||
determined by invoking the VCS (Git currently). | ||
*/ | ||
static Package load(NativePath root, NativePath recipe_file = NativePath.init, Package parent = null, string version_override = "") | ||
static Package load(NativePath root, | ||
NativePath recipe_file = NativePath.init, Package parent = null, | ||
string version_override = "", NativePath scm_path = NativePath.init) | ||
Comment on lines
-153
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
{ | ||
import dub.recipe.io; | ||
|
||
|
@@ -163,7 +171,7 @@ class Package { | |
|
||
auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null); | ||
|
||
auto ret = new Package(recipe, root, parent, version_override); | ||
auto ret = new Package(recipe, root, parent, version_override, scm_path); | ||
Comment on lines
-166
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
ret.m_infoFile = recipe_file; | ||
return ret; | ||
} | ||
|
@@ -742,21 +750,24 @@ class Package { | |
} | ||
} | ||
|
||
private string determineVersionFromSCM(NativePath path) | ||
private string determineVersionFromSCM(NativePath path, NativePath scm_path) | ||
{ | ||
if (scm_path.empty) { | ||
scm_path = path ~ ".git"; | ||
} | ||
Comment on lines
+755
to
+757
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't do this |
||
// On Windows, which is slow at running external processes, | ||
// cache the version numbers that are determined using | ||
// GIT to speed up the initialization phase. | ||
// Git to speed up the initialization phase. | ||
version (Windows) { | ||
import std.file : exists, readText; | ||
|
||
// quickly determine head commit without invoking GIT | ||
// quickly determine head commit without invoking Git | ||
string head_commit; | ||
auto hpath = (path ~ ".git/HEAD").toNativeString(); | ||
auto hpath = (scm_path ~ "HEAD").toNativeString(); | ||
Comment on lines
-755
to
+766
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was bad code and this is not an improvement :) |
||
if (exists(hpath)) { | ||
auto head_ref = readText(hpath).strip(); | ||
if (head_ref.startsWith("ref: ")) { | ||
auto rpath = (path ~ (".git/"~head_ref[5 .. $])).toNativeString(); | ||
auto rpath = (scm_path ~ head_ref[5 .. $]).toNativeString(); | ||
if (exists(rpath)) | ||
head_commit = readText(rpath).strip(); | ||
} | ||
|
@@ -775,7 +786,7 @@ private string determineVersionFromSCM(NativePath path) | |
} | ||
|
||
// if no cache file or the HEAD commit changed, perform full detection | ||
auto ret = determineVersionWithGIT(path); | ||
auto ret = determineVersionWithGit(path, scm_path); | ||
|
||
version (Windows) { | ||
// update version cache file | ||
|
@@ -788,16 +799,19 @@ private string determineVersionFromSCM(NativePath path) | |
return ret; | ||
} | ||
|
||
// determines the version of a package that is stored in a GIT working copy | ||
// determines the version of a package that is stored in a Git working copy | ||
// by invoking the "git" executable | ||
private string determineVersionWithGIT(NativePath path) | ||
private string determineVersionWithGit(NativePath path, NativePath git_dir) | ||
{ | ||
import std.process; | ||
import dub.semver; | ||
import std.process; | ||
import std.typecons : tuple; | ||
|
||
auto git_dir = path ~ ".git"; | ||
if (!existsFile(git_dir) || !isDir(git_dir.toNativeString)) return null; | ||
auto git_dir_param = "--git-dir=" ~ git_dir.toNativeString(); | ||
auto git_dir_params = tuple( | ||
"-C", path.toNativeString(), | ||
"--git-dir=" ~ git_dir.relativeTo(path).toNativeString(), | ||
).expand; | ||
|
||
static string exec(scope string[] params...) { | ||
auto ret = executeShell(escapeShellCommand(params)); | ||
|
@@ -806,7 +820,7 @@ private string determineVersionWithGIT(NativePath path) | |
return null; | ||
} | ||
|
||
auto tag = exec("git", git_dir_param, "describe", "--long", "--tags"); | ||
auto tag = exec("git", git_dir_params, "describe", "--long", "--tags"); | ||
if (tag !is null) { | ||
auto parts = tag.split("-"); | ||
auto commit = parts[$-1]; | ||
|
@@ -819,7 +833,7 @@ private string determineVersionWithGIT(NativePath path) | |
} | ||
} | ||
|
||
auto branch = exec("git", git_dir_param, "rev-parse", "--abbrev-ref", "HEAD"); | ||
auto branch = exec("git", git_dir_params, "rev-parse", "--abbrev-ref", "HEAD"); | ||
if (branch !is null) { | ||
if (branch != "HEAD") return "~" ~ branch; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#!/usr/bin/env bash | ||
set -e | ||
|
||
. $(dirname "${BASH_SOURCE[0]}")/common.sh | ||
|
||
LAST_DIR=$PWD | ||
TEMP_DIR="submodule-test" | ||
|
||
function cleanup { | ||
cd "$LAST_DIR" | ||
rm -rf "$TEMP_DIR" | ||
} | ||
trap cleanup EXIT | ||
|
||
mkdir "$TEMP_DIR" | ||
cd "$TEMP_DIR" | ||
|
||
mkdir -p dependency/src | ||
|
||
cat << EOF >> dependency/dub.sdl | ||
name "dependency" | ||
sourcePaths "src" | ||
EOF | ||
|
||
cat << EOF >> dependency/src/foo.d | ||
module foo; | ||
void foo() { } | ||
EOF | ||
|
||
function git_ { | ||
git -C dependency -c "user.name=Name" -c "user.email=Email" "$@" | ||
} | ||
git_ init | ||
git_ add dub.sdl | ||
git_ add src/foo.d | ||
git_ commit -m "first commit" | ||
git_ tag v1.0.0 | ||
|
||
mkdir project | ||
|
||
cat << EOF >> project/dub.sdl | ||
name "project" | ||
mainSourceFile "project.d" | ||
targetType "executable" | ||
dependency "dependency" version="1.0.0" | ||
EOF | ||
|
||
cat << EOF >> project/project.d | ||
module project; | ||
import foo : foo; | ||
void main() { foo(); } | ||
EOF | ||
|
||
function git_ { | ||
git -C project -c "user.name=Name" -c "user.email=Email" "$@" | ||
} | ||
git_ init | ||
git_ add dub.sdl | ||
git_ add project.d | ||
git_ submodule add ../dependency dependency | ||
git_ commit -m "first commit" | ||
|
||
# dub should now pick up the dependency | ||
$DUB --root=project --submodules run | ||
|
||
if ! grep -c -e "\"dependency\": \"1.0.0\"" project/dub.selections.json; then | ||
die $LINENO "Dependency version was not identified correctly." | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is
--git-dir
necessary here? It was necessary in conjunction with--work-tree
before-C
was introduced.