update_deps/build_graph.ps1 (156 lines of code) (raw):
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
<#
.SYNOPSIS
Given a comma-separeted list of URLs to repositories, prints the order in which its submodules should be updated to file order.json
.DESCRIPTION
Performs bottom-up level-order traversal of the dependency graph and prints the order to file order.json
.PARAMETER root_list
Comma-separated list of URLs for repositories upto which updates must be propagated
.INPUTS
None.
.OUTPUTS
Prints order in which repositories must be updated to file order.json
.EXAMPLE
PS> .\build_graph.ps1 -root_list 'https://msazure.visualstudio.com/DefaultCollection/One/_git/Azure-Messaging-GeoReplication', 'https://msazure.visualstudio.com/DefaultCollection/One/_git/Azure-Messaging-ElasticLog'
PS> Get-Content -Path order.json
[
"c-build-tools",
"macro-utils-c",
"c-logging",
"ctest",
"c-testrunnerswitcher",
"umock-c",
"c-pal",
"c-util",
"com-wrapper",
"sf-c-util",
"clds",
"zrpc",
"Azure-Messaging-Metrics",
"Azure-MessagingStore",
"Azure-Messaging-GeoReplication",
"Azure-Messaging-ElasticLog"
]
#>
param(
[Parameter(Mandatory=$true)][string[]] $root_list # comma-separated list of URLs for repositories upto which updates must be propagated
)
# parse repo URL to extract repo name
# Expected URL format: */<repo_name>[.*]
# Example: https://github.com/Azure/c-build-tools or https://github.com/Azure/c-build-tools.git
function get-name-from-url {
param (
[string] $url
)
if(!$url.Contains("http"))
{
Write-Error("Invalid URL: $url")
exit -1
}
$split_by_slash = $url.Split('/')
$split_by_dot = $split_by_slash[-1].Split('.') # $split_by_slash[-1] contains [repo_name].git
return $split_by_dot[0] # $split_by_dot[0] contains [repo_name]
}
# get list of submodule URLs from a given repo URL
function get-submodules {
param (
[string] $url
)
$name = get-name-from-url $url
# get raw submodule data, needs to be parsed
$submodule_data = git config -f $name\.gitmodules --get-regexp url
# create list for submodule URLs
$submodules = New-Object -TypeName "System.Collections.ArrayList"
# return empty list if no submodules
if (!$submodule_data) {
return $submodules
}
# split raw data to parse
$submodule_tokens = $submodule_data.Split('')
# create uri object for base URL
$base_uri = [System.Uri]::new($url + "/")
# iterate over tokens
for($i = 0; $i -lt $submodule_tokens.Length; $i++) {
# odd tokens contain URLs
if($i % 2 -ne 0) {
$submodule_uri = $submodule_tokens[$i]
# convert relative URLs to absolute
if(-not $submodule_tokens[$i].StartsWith("http")){
$submodule_uri = [System.Uri]::new($base_uri, $submodule_tokens[$i]).AbsoluteUri
}
# append URL to list
[void]$submodules.Add($submodule_uri)
}
}
return $submodules
}
# dictionary to store mapping from repo name to level in dependency graph
# root repo is level 0 and leaf repo is maximum level
$repo_levels = New-Object -TypeName "System.Collections.Generic.Dictionary[string, int]"
# queue to perform breadth-first search
$queue = New-Object -TypeName "System.Collections.Queue"
# get list of repos to ignore while building graph from ignores.json
$path_to_ignores = $PSScriptRoot + "\ignores.json"
$repos_to_ignore = (Get-Content -Path $path_to_ignores) | ConvertFrom-Json
# for spinner animation
$progress = @('|','/','-','\')
$progress_counter = 0
# perform breadth-first search on dependency graph
function Build-Graph {
# spinner animation
Write-Host "`b$($progress[$progress_counter++ % $progress.Length])" -NoNewline -ForegroundColor Yellow
# get front of queue
$repo_url = $queue.Dequeue()
$repo_name = get-name-from-url -url $repo_url
# set repo level to 0 if not seen before
if(-not $repo_levels.ContainsKey($repo_name)) {
$repo_levels[$repo_name] = 0
}
# clone repo if not already present
if(-not (Test-Path -Path $repo_name)) {
Write-Host "`b" -NoNewline # clear spinner
git clone $repo_url
}
# $repo_level is the length of the path in the graph from the root to the current repo
$repo_level = $repo_levels[$repo_name]
# get list for submodules URLs
$submodules = get-submodules $repo_url
# iterate of list of submodules
foreach($submodule in $submodules) {
$submodule_name = get-name-from-url -url $submodule
# ignore submodule if it is i $repos_to_ignore
if ($submodule_name -in $repos_to_ignore) {
continue
}
# $level is the length of the longest path in the graph from the root to the submodule seen so far
$level = 0
[void]$repo_levels.TryGetValue($submodule_name, [ref]$level)
# update repo level of submodule if path from root to submodule via current repo is longer
if (($repo_level+1) -gt $level) {
$repo_levels[$submodule_name] = $repo_level+1
}
# add submodule to queue
$queue.Enqueue($submodule)
}
}
# seed queue with given arguments
foreach ($root in $root_list) {
$queue.Enqueue($root)
}
# build dependency graph
while ( $queue.Count -ne 0) {
Build-Graph
}
# clear spinner animation
Write-Host "`b"-NoNewLine
# convert dictionary to list of (repo_name, level)
$repo_levels_list = [Linq.Enumerable]::ToList($repo_levels)
# sort list by descending order of level
$repo_levels_list.Sort({$args[1].Value.CompareTo($args[0].Value)})
# create list to hold repos in order to be updated
$repo_order = New-Object -TypeName "System.Collections.ArrayList"
# collect repo names in repo_order
$repo_levels_list.ForEach({$repo_order.Add($args[0].Key)})
Set-Content -Path .\order.json -Value ($repo_order | ConvertTo-Json)
Exit 0